ruby_gaurden 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dc546e0183aa5317cd7f463490262c6929e20a8a5261dd6731d6578abce11907
4
+ data.tar.gz: c8fdd3f6d9777ff56780fd933f21cd34432e8dd80656019853fdb0c3e9ec9ee1
5
+ SHA512:
6
+ metadata.gz: a2a9efa813c4d0cf4eca8471b0d93bdce036ba946e0ef3373148f8993d00d606535b6200671ae03832d0af83bef0e1e25397e502f49223342f88c30a1997526f
7
+ data.tar.gz: 5f9892a85b6b83826556f797f6ba6ea709a0307ee4a52665492121d96a261026a405a514dcb3fd152930934bcac910c8c0ad256987472e5bffaa99155287d457
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+
5
+ module RubyGaurden
6
+ class Bed
7
+ include Execution
8
+ include RuntimeEnvironment
9
+ include Bindings
10
+ include Bridging
11
+ include ThreadSafety
12
+
13
+ # Creates a new instance of the Bed and executes the provided source.
14
+ # @param args [Array] Arguments passed to #execute.
15
+ # @return [Object] The result of the execution.
16
+ # @see #execute
17
+ def self.execute(...)
18
+ new.execute(...)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+
5
+ module RubyGaurden
6
+ class BedError < Error
7
+ def self.[](class_name)
8
+ bed_class_name = :"Bed#{class_name}"
9
+
10
+ if const_defined?(bed_class_name) && (klass = const_get(bed_class_name)) < self
11
+ klass
12
+ else
13
+ const_set(bed_class_name, Class.new(self))
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+
5
+ module RubyGaurden
6
+ module Bindings
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ def bindings
11
+ @bindings ||= if superclass.respond_to?(:bindings)
12
+ superclass.bindings.dup
13
+ else
14
+ []
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def binds(target, proc = nil, &block)
21
+ actual_proc = proc || block || -> {}
22
+ bindings << [target, actual_proc]
23
+ end
24
+ end
25
+
26
+ def initialize(*)
27
+ super
28
+
29
+ self.class.bindings.each do |target, proc|
30
+ bind target, lambda { |*args|
31
+ instance_exec(*args, &proc)
32
+ }
33
+ end
34
+ end
35
+
36
+ def bind(target, proc = -> {})
37
+ context.attach(target, proc)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+ require 'json'
5
+ require 'active_support/concern'
6
+
7
+ module RubyGaurden
8
+ module Bridging
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ binds('__rb_exit') { |status| raise Error, "Exit with status #{status.inspect}" }
13
+ binds('__rb_stdout_write') { |data| stdout << data }
14
+ binds('__rb_stderr_write') { |data| stderr << data }
15
+
16
+ executes <<-RUBY
17
+ # backtick_javascript: true
18
+ require 'native'
19
+ require 'singleton'
20
+ require 'json'
21
+
22
+ module RubyGaurden
23
+ VERSION = #{RubyGaurden::VERSION.inspect}
24
+
25
+ class CurrentBedProxy
26
+ include Singleton
27
+ end
28
+
29
+ def self.planted?
30
+ true
31
+ end
32
+
33
+ def self.current
34
+ CurrentBedProxy.instance
35
+ end
36
+
37
+ # Handles method invocation from the host, ensuring data is correctly
38
+ # translated and errors are caught.
39
+ def self.__invoke_from_host(method_name, args_json)
40
+ args = JSON.parse(args_json)
41
+ value = ::Object.send(method_name, *args)
42
+
43
+ { isCaught: false, val: value.to_json }
44
+ rescue Exception => e
45
+ { isCaught: true, val: [e.class.name, e.message].to_json }
46
+ end
47
+
48
+ # Handles code evaluation from the host.
49
+ def self.__eval_from_host(js_source)
50
+ value = `eval(js_source)`
51
+ { isCaught: false, val: value.to_json }
52
+ rescue Exception => e
53
+ { isCaught: true, val: [e.class.name, e.message].to_json }
54
+ end
55
+ end
56
+
57
+ module Kernel
58
+ def exit(status = 0)
59
+ `__rb_exit(status)`
60
+ end
61
+ end
62
+
63
+ $stdout.write_proc = ->(data) { `__rb_stdout_write(data)` }
64
+ $stderr.write_proc = ->(data) { `__rb_stderr_write(data)` }
65
+
66
+ `
67
+ globalThis.__rb_bridge_invoke = function(methodName, argsJson) {
68
+ return Opal.RubyGaurden.$__invoke_from_host(methodName, argsJson).$to_n();
69
+ };
70
+
71
+ globalThis.__rb_eval_wrapper = function(source) {
72
+ return Opal.RubyGaurden.$__eval_from_host(source).$to_n();
73
+ };
74
+ `
75
+ RUBY
76
+ end
77
+
78
+ class_methods do
79
+ # Exposes host methods to the sandbox, allowing sandboxed code to call
80
+ # back into the main Ruby process safely via JSON serialization.
81
+ # @param method_names [Array<Symbol, String>] Methods on the host to expose.
82
+ # @note Blocks are not supported for bridged methods.
83
+ def exposes(*method_names)
84
+ method_names.each do |method_name|
85
+ handle = "__rb_expose_#{method_name}"
86
+ binds(handle, ->(json) { send(method_name, *JSON.parse(json)).to_json })
87
+
88
+ executes(<<-RUBY)
89
+ # backtick_javascript: true
90
+ def (RubyGaurden::CurrentBedProxy.instance).#{method_name}(*args, &block)
91
+ raise ArgumentError, "Blocks not supported" if block
92
+ JSON.parse(`#{handle}(\#{args.to_json})`)
93
+ end
94
+ RUBY
95
+ end
96
+ end
97
+ end
98
+
99
+ # Returns the accumulated stdout produced by the sandbox.
100
+ # @return [Array<String>]
101
+ def stdout
102
+ @stdout ||= []
103
+ end
104
+
105
+ # Returns the accumulated stderr produced by the sandbox.
106
+ # @return [Array<String>]
107
+ def stderr
108
+ @stderr ||= []
109
+ end
110
+
111
+ # Clears the IO buffers (stdout and stderr).
112
+ def reset_io!
113
+ @stdout = []
114
+ @stderr = []
115
+ end
116
+
117
+ # Directly invokes a Ruby method defined inside the sandbox.
118
+ # This bypasses the Ruby-to-JS compilation step for the call itself.
119
+ # @param method_name [Symbol, String] The name of the method to call.
120
+ # @param args [Array] Arguments to pass to the method.
121
+ # @return [Object] The result of the method call.
122
+ # @raise [BedError] if the method raises an exception inside the sandbox.
123
+ def call(method_name, *args)
124
+ # We use JSON to move data across the bridge to avoid V8/Ruby object mapping overhead
125
+ # and to ensure Opal objects are correctly initialized.
126
+ serialized_args = args.to_json
127
+ result = context.call('__rb_bridge_invoke', method_name.to_s, serialized_args)
128
+ handle_bridge_result(result)
129
+ end
130
+
131
+ private
132
+
133
+ def eval_compiled_source(source)
134
+ result = context.call('__rb_eval_wrapper', source)
135
+ handle_bridge_result(result) # result is the Ruby Hash returned by MiniRacer
136
+ rescue MiniRacer::ScriptTerminatedError => e
137
+ raise TimeoutError, e.message
138
+ rescue MiniRacer::RuntimeError, StandardError => e
139
+ raise e if e.is_a?(RubyGaurden::Error)
140
+
141
+ raise ExecutionError, e.message
142
+ end
143
+
144
+ def handle_bridge_result(result)
145
+ return unless result.is_a?(Hash)
146
+
147
+ if result.fetch('isCaught', false)
148
+ class_name, message = result_value(result)
149
+ raise BedError[class_name], message
150
+ end
151
+
152
+ result_value(result)
153
+ rescue MiniRacer::RuntimeError, StandardError => e
154
+ raise e if e.is_a?(RubyGaurden::Error)
155
+
156
+ raise ExecutionError, e.message
157
+ end
158
+
159
+ def result_value(result)
160
+ result['val'] ? JSON.parse(result['val'], quirks_mode: true) : nil
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+
5
+ module RubyGaurden
6
+ class CompilationError < Error
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+
5
+ module RubyGaurden
6
+ class Error < StandardError
7
+ end
8
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+ require 'active_support/concern'
5
+ require 'mini_racer'
6
+
7
+ module RubyGaurden
8
+ module Execution
9
+ extend ActiveSupport::Concern
10
+
11
+ DEFAULT_MAXIMUM_EXECUTION_TIME = 1000
12
+ DEFAULT_MAX_MEMORY = 512 * 1024 * 1024 # 512MB default limit
13
+
14
+ class_methods do
15
+ def maximum_execution_time
16
+ @maximum_execution_time ||= superclass.maximum_execution_time if superclass.respond_to?(:maximum_execution_time)
17
+ end
18
+
19
+ def maximum_memory
20
+ @maximum_memory ||= superclass.maximum_memory if superclass.respond_to?(:maximum_memory)
21
+ end
22
+
23
+ # Sets the maximum time a script can execute before being terminated.
24
+ # @param seconds [Integer, Float] Time in seconds.
25
+ def times_out_in(seconds)
26
+ @maximum_execution_time = seconds
27
+ end
28
+
29
+ # Sets the maximum memory the V8 context can consume.
30
+ # @param bytes [Integer] Memory limit in bytes.
31
+ def limits_memory_to(bytes)
32
+ @maximum_memory = bytes
33
+ end
34
+
35
+ # Accesses the thread-safe pool of pre-warmed V8 contexts.
36
+ # @return [Thread::Queue]
37
+ def context_pool
38
+ @context_pool ||= ::Queue.new
39
+ end
40
+
41
+ # Pre-warms a specified number of V8 contexts and adds them to the pool.
42
+ # @param size [Integer] The number of contexts to create.
43
+ def warm_up(size = 1)
44
+ size.times { context_pool.push(create_context) }
45
+ end
46
+
47
+ # Creates a fresh V8 context and evaluates the initialization source.
48
+ # Use this to bypass the pool if needed or to manually populate it.
49
+ # @return [MiniRacer::Context]
50
+ def create_context
51
+ # Use a large timeout (30s) for initialization to ensure the large Opal
52
+ # runtime loads even if the user set a small timeout for their code.
53
+ MiniRacer::Context.new(
54
+ timeout: (maximum_execution_time || DEFAULT_MAXIMUM_EXECUTION_TIME) * 1000,
55
+ max_memory: maximum_memory || DEFAULT_MAX_MEMORY
56
+ ).tap do |ctx|
57
+ # We use a secondary timeout for the init phase. If this fails,
58
+ # the context is not returned/tapped, preventing a "broken"
59
+ # context from entering the pool or being used by an instance.
60
+ ctx.eval(send(:initialization_source), timeout: 30_000)
61
+ end
62
+ end
63
+ end
64
+
65
+ def maximum_execution_time
66
+ @maximum_execution_time ||= self.class.maximum_execution_time
67
+ end
68
+
69
+ def maximum_memory
70
+ @maximum_memory ||= self.class.maximum_memory
71
+ end
72
+
73
+ def maximum_execution_time_ms
74
+ return unless maximum_execution_time
75
+
76
+ maximum_execution_time * 1000
77
+ end
78
+
79
+ def context
80
+ @context ||= checkout_context
81
+ end
82
+
83
+ private
84
+
85
+ def checkout_context
86
+ self.class.context_pool.pop(true)
87
+ rescue ThreadError
88
+ self.class.create_context
89
+ end
90
+
91
+ def eval_compiled_source(source)
92
+ context.eval source
93
+ rescue MiniRacer::RuntimeError => e
94
+ raise ExecutionError, e.message
95
+ rescue MiniRacer::ScriptTerminatedError => e
96
+ raise TimeoutError, e.message
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+
5
+ module RubyGaurden
6
+ class ExecutionError < Error
7
+ end
8
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+ require 'active_support/concern'
5
+ require 'opal'
6
+
7
+ module RubyGaurden
8
+ module RuntimeEnvironment
9
+ extend ActiveSupport::Concern
10
+
11
+ MAX_CACHE_SIZE = 1000
12
+
13
+ included do
14
+ uses 'opal'
15
+ requires 'opal'
16
+ end
17
+
18
+ class_methods do
19
+ def compiled_cache
20
+ @compiled_cache ||= {}
21
+ end
22
+
23
+ private
24
+
25
+ # Declares a gem dependency to be loaded into the sandbox.
26
+ # @param gem_names [Array<String>] List of gem names.
27
+ def uses(*gem_names)
28
+ @uses ||= []
29
+ @uses += gem_names
30
+ end
31
+
32
+ # Declares a file or feature to be required within the sandbox.
33
+ # @param paths [Array<String>] List of paths to require.
34
+ def requires(*paths)
35
+ @requires ||= []
36
+ @requires += paths
37
+ end
38
+
39
+ # Defines Ruby code to be executed during the initialization of every sandbox instance.
40
+ # Useful for setting up global state or helper classes.
41
+ # @param source [String] The Ruby code to execute during boot.
42
+ def executes(source)
43
+ @executes ||= []
44
+ @executes << source
45
+ end
46
+
47
+ def initialization_source
48
+ @initialization_source ||= build_initialization_source
49
+ rescue SyntaxError, StandardError => e
50
+ raise CompilationError, e.message
51
+ end
52
+
53
+ def build_initialization_source
54
+ builder = Opal::Builder.new(compiler_options: { arity_check: true, dynamic_require_severity: :error })
55
+ builder.build('opal')
56
+
57
+ # Ensure the builder can resolve requirements by including the gems
58
+ inherited_values(:@uses).uniq.each { |g| builder.use_gem(g) }
59
+ inherited_values(:@requires).uniq.each { |p| builder.build(p) }
60
+ inherited_values(:@executes).each { |s| builder.build_str(s, '(executes)') }
61
+
62
+ builder.to_s
63
+ end
64
+
65
+ def inherited_values(ivar)
66
+ ancestors.reverse.flat_map do |ancestor|
67
+ ancestor.instance_variable_defined?(ivar) ? ancestor.instance_variable_get(ivar) : []
68
+ end
69
+ end
70
+ end
71
+
72
+ # Executes a string of Ruby code within the sandbox instance.
73
+ # Results are cached by the source string to optimize repeated calls.
74
+ # @param source [String] The Ruby code to execute.
75
+ # @return [Object] The result of the execution, translated to host Ruby objects.
76
+ # @raise [CompilationError] if the code has syntax errors.
77
+ # @raise [ExecutionError] if the code raises an exception during runtime.
78
+ def execute(source)
79
+ js = self.class.compiled_cache[source] || compile_and_cache(source)
80
+ eval_compiled_source(js)
81
+ end
82
+
83
+ # Compiles Ruby source to JavaScript and clears the cache if the limit is reached.
84
+ # @param source [String] Ruby source code.
85
+ # @return [String] Compiled JavaScript.
86
+ def compile_and_cache(source)
87
+ prune_cache! if self.class.compiled_cache.size >= MAX_CACHE_SIZE
88
+ self.class.compiled_cache[source] = Opal::Compiler.new(source, file: '(execute)', arity_check: true).compile
89
+ rescue SyntaxError => e
90
+ raise CompilationError, e.message
91
+ end
92
+
93
+ # Prune the oldest 10% of entries (at least 1) to avoid performance cliffs.
94
+ # Since Ruby Hashes maintain insertion order, this behaves like FIFO.
95
+ def prune_cache!
96
+ self
97
+ .class
98
+ .compiled_cache
99
+ .keys
100
+ .first([1, MAX_CACHE_SIZE / 10].max)
101
+ .each { |k| self.class.compiled_cache.delete(k) }
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+ require 'active_support/concern'
5
+ require 'monitor'
6
+
7
+ module RubyGaurden
8
+ module ThreadSafety
9
+ extend ActiveSupport::Concern
10
+
11
+ class_methods do
12
+ def maximum_execution_time
13
+ synchronize { super }
14
+ end
15
+
16
+ def initialization_source
17
+ synchronize { super }
18
+ end
19
+
20
+ def bindings
21
+ synchronize { super }
22
+ end
23
+
24
+ private
25
+
26
+ def times_out_in(...)
27
+ synchronize { super }
28
+ end
29
+
30
+ def uses(...)
31
+ synchronize { super }
32
+ end
33
+
34
+ def requires(...)
35
+ synchronize { super }
36
+ end
37
+
38
+ def executes(...)
39
+ synchronize { super }
40
+ end
41
+
42
+ def binds(...)
43
+ synchronize { super }
44
+ end
45
+
46
+ def exposes(...)
47
+ synchronize { super }
48
+ end
49
+
50
+ def synchronize(&)
51
+ monitor.synchronize(&)
52
+ end
53
+
54
+ def monitor
55
+ @monitor ||= Monitor.new
56
+ end
57
+ end
58
+
59
+ def maximum_execution_time
60
+ synchronize { super }
61
+ end
62
+
63
+ def execute(...)
64
+ synchronize { super }
65
+ end
66
+
67
+ def synchronize(&)
68
+ monitor.synchronize(&)
69
+ end
70
+
71
+ def monitor
72
+ @monitor ||= Monitor.new
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden'
4
+
5
+ module RubyGaurden
6
+ class TimeoutError < Error
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyGaurden
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_gaurden/version'
4
+
5
+ module RubyGaurden
6
+ autoload :Bindings, 'ruby_gaurden/bindings'
7
+ autoload :BedError, 'ruby_gaurden/bed_error'
8
+ autoload :Bridging, 'ruby_gaurden/bridging'
9
+ autoload :CompilationError, 'ruby_gaurden/compilation_error'
10
+ autoload :Error, 'ruby_gaurden/error'
11
+ autoload :Execution, 'ruby_gaurden/execution'
12
+ autoload :ExecutionError, 'ruby_gaurden/execution_error'
13
+ autoload :Bed, 'ruby_gaurden/bed'
14
+ autoload :RuntimeEnvironment, 'ruby_gaurden/runtime_environment'
15
+ autoload :ThreadSafety, 'ruby_gaurden/thread_safety'
16
+ autoload :TimeoutError, 'ruby_gaurden/timeout_error'
17
+
18
+ module_function
19
+
20
+ # Checks if the current execution context is inside a sandbox.
21
+ # @return [Boolean] true if inside a sandbox, false otherwise.
22
+ def planted?
23
+ false
24
+ end
25
+
26
+ # Returns the current sandbox proxy instance if running inside a sandbox.
27
+ # @return [Object, nil] The proxy instance or nil if outside a sandbox.
28
+ def current
29
+ nil
30
+ end
31
+
32
+ # Convenience method to execute Ruby code in a fresh, one-off sandbox.
33
+ # @param args [Array] Arguments passed to Bed.execute.
34
+ # @return [Object] The result of the execution.
35
+ def execute(...)
36
+ Bed.execute(...)
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_gaurden
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tayler Phillips
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-05-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: mini_racer
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.21.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.21.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: opal
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '='
45
+ - !ruby/object:Gem::Version
46
+ version: 1.8.0
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.8.0
54
+ email:
55
+ - taylerphillips20@gmail.com
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - lib/ruby_gaurden.rb
61
+ - lib/ruby_gaurden/bed.rb
62
+ - lib/ruby_gaurden/bed_error.rb
63
+ - lib/ruby_gaurden/bindings.rb
64
+ - lib/ruby_gaurden/bridging.rb
65
+ - lib/ruby_gaurden/compilation_error.rb
66
+ - lib/ruby_gaurden/error.rb
67
+ - lib/ruby_gaurden/execution.rb
68
+ - lib/ruby_gaurden/execution_error.rb
69
+ - lib/ruby_gaurden/runtime_environment.rb
70
+ - lib/ruby_gaurden/thread_safety.rb
71
+ - lib/ruby_gaurden/timeout_error.rb
72
+ - lib/ruby_gaurden/version.rb
73
+ homepage: https://github.com/Phillita/ruby_gaurden
74
+ licenses:
75
+ - MIT
76
+ metadata:
77
+ allowed_push_host: https://rubygems.org
78
+ source_code_uri: https://github.com/Phillita/ruby_gaurden
79
+ changelog_uri: https://github.com/Phillita/ruby_gaurden/blob/main/CHANGELOG.md
80
+ bug_tracker_uri: https://github.com/Phillita/ruby_gaurden/issues
81
+ rubygems_mfa_required: 'true'
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 3.1.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.6.2
97
+ specification_version: 4
98
+ summary: RubyGaurden allows the execution of untrusted Ruby code safely in a walled
99
+ garden.
100
+ test_files: []