safe_ruby 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,272 @@
1
+ # Copyright (c) 2018 Uku Taht
2
+ # frozen_string_literal: true
3
+
4
+ IO_S_METHODS = %w[
5
+ new
6
+ foreach
7
+ open
8
+ ].freeze
9
+
10
+ KERNEL_S_METHODS = %w[
11
+ Array
12
+ binding
13
+ block_given?
14
+ catch
15
+ chomp
16
+ chomp!
17
+ chop
18
+ chop!
19
+ eval
20
+ fail
21
+ Float
22
+ format
23
+ global_variables
24
+ gsub
25
+ gsub!
26
+ Integer
27
+ iterator?
28
+ lambda
29
+ local_variables
30
+ loop
31
+ method_missing
32
+ proc
33
+ raise
34
+ scan
35
+ split
36
+ sprintf
37
+ String
38
+ sub
39
+ sub!
40
+ throw
41
+ ].freeze
42
+
43
+ SYMBOL_S_METHODS = %w[
44
+ all_symbols
45
+ ].freeze
46
+
47
+ STRING_S_METHODS = [].freeze
48
+
49
+ KERNEL_METHODS = %w[
50
+ ==
51
+
52
+ ray
53
+ nding
54
+ ock_given?
55
+ tch
56
+ omp
57
+ omp!
58
+ op
59
+ op!
60
+ ass
61
+ clone
62
+ dup
63
+ eql?
64
+ equal?
65
+ eval
66
+ fail
67
+ Float
68
+ format
69
+ freeze
70
+ frozen?
71
+ global_variables
72
+ gsub
73
+ gsub!
74
+ hash
75
+ id
76
+ initialize_copy
77
+ inspect
78
+ instance_eval
79
+ instance_of?
80
+ instance_variables
81
+ instance_variable_get
82
+ instance_variable_set
83
+ instance_variable_defined?
84
+ Integer
85
+ is_a?
86
+ iterator?
87
+ kind_of?
88
+ lambda
89
+ local_variables
90
+ loop
91
+ methods
92
+ method_missing
93
+ nil?
94
+ private_methods
95
+ print
96
+ proc
97
+ protected_methods
98
+ public_methods
99
+ raise
100
+ remove_instance_variable
101
+ respond_to?
102
+ respond_to_missing?
103
+ scan
104
+ send
105
+ singleton_methods
106
+ singleton_method_added
107
+ singleton_method_removed
108
+ singleton_method_undefined
109
+ split
110
+ sprintf
111
+ String
112
+ sub
113
+ sub!
114
+ taint
115
+ tainted?
116
+ throw
117
+ to_a
118
+ to_s
119
+ type
120
+ untaint
121
+ __send__
122
+ ].freeze
123
+
124
+ NILCLASS_METHODS = %w[
125
+ &
126
+ inspect
127
+ nil?
128
+ to_a
129
+ to_f
130
+ to_i
131
+ to_s
132
+ ^
133
+ |
134
+ ].freeze
135
+
136
+ SYMBOL_METHODS = %w[
137
+ ===
138
+ id2name
139
+ inspect
140
+ to_i
141
+ to_int
142
+ to_s
143
+ to_sym
144
+ ].freeze
145
+
146
+ TRUECLASS_METHODS = %w[
147
+ &
148
+ to_s
149
+ ^
150
+ |
151
+ ].freeze
152
+
153
+ FALSECLASS_METHODS = %w[
154
+ &
155
+ to_s
156
+ ^
157
+ |
158
+ ].freeze
159
+
160
+ ENUMERABLE_METHODS = %w[
161
+ all?
162
+ any?
163
+ collect
164
+ detect
165
+ each_with_index
166
+ entries
167
+ find
168
+ find_all
169
+ grep
170
+ include?
171
+ inject
172
+ map
173
+ max
174
+ member?
175
+ min
176
+ partition
177
+ reject
178
+ select
179
+ sort
180
+ sort_by
181
+ to_a
182
+ zip
183
+ ].freeze
184
+
185
+ STRING_METHODS = %w[
186
+ %
187
+ *
188
+ +
189
+ <<
190
+ <=>
191
+ ==
192
+ =~
193
+ capitalize
194
+ capitalize!
195
+ casecmp
196
+ center
197
+ chomp
198
+ chomp!
199
+ chop
200
+ chop!
201
+ concat
202
+ count
203
+ crypt
204
+ delete
205
+ delete!
206
+ downcase
207
+ downcase!
208
+ dump
209
+ each
210
+ each_byte
211
+ each_line
212
+ empty?
213
+ eql?
214
+ gsub
215
+ gsub!
216
+ hash
217
+ hex
218
+ include?
219
+ index
220
+ initialize
221
+ initialize_copy
222
+ insert
223
+ inspect
224
+ intern
225
+ length
226
+ ljust
227
+ lines
228
+ lstrip
229
+ lstrip!
230
+ match
231
+ next
232
+ next!
233
+ oct
234
+ replace
235
+ reverse
236
+ reverse!
237
+ rindex
238
+ rjust
239
+ rstrip
240
+ rstrip!
241
+ scan
242
+ size
243
+ slice
244
+ slice!
245
+ split
246
+ squeeze
247
+ squeeze!
248
+ strip
249
+ strip!
250
+ start_with?
251
+ sub
252
+ sub!
253
+ succ
254
+ succ!
255
+ sum
256
+ swapcase
257
+ swapcase!
258
+ to_f
259
+ to_i
260
+ to_s
261
+ to_str
262
+ to_sym
263
+ tr
264
+ tr!
265
+ tr_s
266
+ tr_s!
267
+ upcase
268
+ upcase!
269
+ upto
270
+ []
271
+ []=
272
+ ].freeze
@@ -1,36 +1,56 @@
1
- require 'tempfile'
1
+ # Copyright (c) 2018 Uku Taht
2
+ # frozen_string_literal: true
3
+
4
+ require('childprocess')
5
+ require('tempfile')
2
6
 
3
7
  class EvalError < StandardError
4
- def initialize(msg); super; end
5
8
  end
6
9
 
10
+ # main class
7
11
  class SafeRuby
8
- DEFAULTS = { timeout: 5,
9
- raise_errors: true }
12
+ DEFAULTS = {
13
+ timeout: 5,
14
+ raise_errors: true
15
+ }.freeze
16
+ private_constant :DEFAULTS
10
17
 
11
- def initialize(code, options={})
18
+ # rubocop:disable Style/OptionHash
19
+ def self.eval(code, options = {})
20
+ new(code, options).eval
21
+ end
22
+ # rubocop:enable Style/OptionHash
23
+
24
+ def self.check(code, expected)
25
+ # rubocop:disable Security/Eval
26
+ eval(code) == eval(expected)
27
+ # rubocop:enable Security/Eval
28
+ end
29
+
30
+ # rubocop:disable Style/OptionHash
31
+ def initialize(code, options = {})
12
32
  options = DEFAULTS.merge(options)
13
33
 
14
34
  @code = code
15
35
  @raise_errors = options[:raise_errors]
16
36
  @timeout = options[:timeout]
17
37
  end
38
+ # rubocop:enable Style/OptionHash
18
39
 
19
- def self.eval(code, options={})
20
- new(code, options).eval
21
- end
22
-
40
+ # rubocop:disable Metrics/AbcSize
41
+ # rubocop:disable Metrics/MethodLength
23
42
  def eval
24
43
  temp = build_tempfile
25
44
  read, write = IO.pipe
26
- ChildProcess.build("ruby", temp.path).tap do |process|
45
+ ChildProcess.build('ruby', temp.path).tap do |process|
27
46
  process.io.stdout = write
28
47
  process.io.stderr = write
29
48
  process.start
30
49
  begin
31
50
  process.poll_for_exit(@timeout)
32
51
  rescue ChildProcess::TimeoutError => e
33
- process.stop # tries increasingly harsher methods to kill the process.
52
+ # tries increasingly harsher methods to kill the process.
53
+ process.stop
34
54
  return e.message
35
55
  end
36
56
  write.close
@@ -39,30 +59,29 @@ class SafeRuby
39
59
 
40
60
  data = read.read
41
61
  begin
62
+ # rubocop:disable Security/MarshalLoad
42
63
  Marshal.load(data)
43
- rescue => e
44
- if @raise_errors
45
- raise data
46
- else
47
- return data
48
- end
49
- end
50
- end
64
+ # rubocop:enable Security/MarshalLoad
65
+ rescue StandardError
66
+ raise(data) if @raise_errors
51
67
 
52
- def self.check(code, expected)
53
- eval(code) == eval(expected)
68
+ data
69
+ end
54
70
  end
55
-
71
+ # rubocop:enable Metrics/AbcSize
72
+ # rubocop:enable Metrics/MethodLength
56
73
 
57
74
  private
58
75
 
59
76
  def build_tempfile
60
77
  file = Tempfile.new('saferuby')
61
78
  file.write(MAKE_SAFE_CODE)
62
- file.write <<-STRING
63
- result = eval(%q(#{@code}))
64
- print Marshal.dump(result)
65
- STRING
79
+ file.write(
80
+ <<~STRING
81
+ result = eval(%q(#{@code}))
82
+ print Marshal.dump(result)
83
+ STRING
84
+ )
66
85
  file.rewind
67
86
  file
68
87
  end
@@ -1,7 +1,16 @@
1
+ # Copyright (c) 2018 Uku Taht
2
+ # frozen_string_literal: true
3
+
4
+ # main class
1
5
  class SafeRuby
2
6
  MAJOR_VERSION = 1
3
7
  MINOR_VERSION = 0
4
- RELEASE_VERSION = 4
8
+ RELEASE_VERSION = 5
9
+
10
+ private_constant :MAJOR_VERSION
11
+ private_constant :MINOR_VERSION
12
+ private_constant :RELEASE_VERSION
5
13
 
6
14
  VERSION = [MAJOR_VERSION, MINOR_VERSION, RELEASE_VERSION].join('.')
15
+ public_constant :VERSION
7
16
  end
data/lib/safe_ruby.rb CHANGED
@@ -1,9 +1,6 @@
1
- require 'childprocess'
2
- require_relative 'method_whitelist'
3
- require_relative 'constant_whitelist'
1
+ # Copyright (c) 2018 Uku Taht
2
+ # frozen_string_literal: true
3
+
4
4
  require_relative 'make_safe_code'
5
5
  require_relative 'safe_ruby/runner'
6
6
  require_relative 'safe_ruby/version'
7
-
8
- class SafeRuby
9
- end
data/safe_ruby.gemspec CHANGED
@@ -1,27 +1,30 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # Copyright (c) 2018 Uku Taht
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path('lib', __dir__)
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
6
  require 'safe_ruby/version'
5
7
 
6
8
  Gem::Specification.new do |s|
9
+ s.required_ruby_version = '>= 2.7'
7
10
  s.name = 'safe_ruby'
8
11
  s.version = SafeRuby::VERSION
9
- s.authors = ["Uku Taht"]
10
- s.email = 'uku.taht@gmail.com'
12
+ s.authors = ['Jérôme Arbez-Gindre', 'Uku Taht']
13
+ s.email = 'jeromearbezgindre@gmail.com'
11
14
 
12
- s.summary = "Run untrusted ruby code in a safe environment"
13
- s.description = "Evaluates ruby code by writing it to a tempfile and spawning a child process. Uses a whitelist of methods and constants to keep, for example one cannot run system commands in the environment created by this gem. The environment created by the untrusted code does not leak out into the parent process."
14
- s.homepage = 'http://rubygems.org/gems/safe_ruby'
15
+ s.summary = 'Run untrusted ruby code in a safe environment'
16
+ s.description = 'Evaluates ruby code by writing it to a tempfile and spawning a child ' \
17
+ 'process. Uses a allowlist of methods and constants to keep, for example ' \
18
+ 'one cannot run system commands in the environment created by this gem. ' \
19
+ 'The environment created by the untrusted code does not leak out into ' \
20
+ 'the parent process.'
21
+ s.homepage = 'https://gitlab.com/defmastership/safe_ruby/'
15
22
  s.license = 'MIT'
16
23
 
17
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
18
25
  s.files = `git ls-files`.split("\n")
19
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
- s.require_paths = ["lib"]
21
-
22
- s.add_runtime_dependency 'childprocess', '>= 0.3.9'
26
+ s.require_paths = ['lib']
23
27
 
24
- s.add_development_dependency 'pry'
25
- s.add_development_dependency 'rake'
26
- s.add_development_dependency 'rspec'
28
+ s.add_dependency('childprocess', '~> 5')
29
+ s.metadata['rubygems_mfa_required'] = 'true'
27
30
  end
@@ -1,73 +1,81 @@
1
+ # Copyright (c) 2018 Uku Taht
2
+ # frozen_string_literal: true
3
+
1
4
  require 'spec_helper'
2
5
 
6
+ MALICIOUS_OPERATIONS = [
7
+ "system('rm *')",
8
+ '`rm *`',
9
+ 'Kernel.abort',
10
+ 'cat spec/spec_helper.rb',
11
+ 'File.class_eval { `echo Hello` }',
12
+ 'FileUtils.class_eval { `echo Hello` }',
13
+ 'Dir.class_eval { `echo Hello` }',
14
+ 'FileTest.class_eval { `echo Hello` }',
15
+ 'File.eval "`echo Hello`"',
16
+ 'FileUtils.eval "`echo Hello`"',
17
+ 'Dir.eval "`echo Hello`"',
18
+ 'FileTest.eval "`echo Hello`"',
19
+ 'File.instance_eval { `echo Hello` }',
20
+ 'FileUtils.instance_eval { `echo Hello` }',
21
+ 'Dir.instance_eval { `echo Hello` }',
22
+ 'FileTest.instance_eval { `echo Hello` }',
23
+ "f=IO.popen('uname'); f.readlines; f.close",
24
+ "IO.binread('/etc/passwd')",
25
+ "IO.read('/etc/passwd')"
26
+ ].freeze
27
+
3
28
  describe SafeRuby do
4
29
  describe '#eval' do
5
30
  it 'allows basic operations' do
6
- expect(SafeRuby.eval('4 + 5')).to eq 9
7
- expect(SafeRuby.eval('[4, 5].map{|n| n+1}')).to eq [5 ,6]
31
+ expect(described_class.eval('4 + 5')).to(eq(9))
8
32
  end
9
33
 
10
- it 'returns correct object' do
11
- expect(SafeRuby.eval('[1,2,3]')).to eq [1,2,3]
34
+ it 'allows basic operations with map' do
35
+ expect(described_class.eval('[4, 5].map{|n| n+1}')).to(eq([5, 6]))
12
36
  end
13
37
 
14
- MALICIOUS_OPERATIONS = [
15
- "system('rm *')",
16
- "`rm *`",
17
- "Kernel.abort",
18
- "cat spec/spec_helper.rb",
19
- "File.class_eval { `echo Hello` }",
20
- "FileUtils.class_eval { `echo Hello` }",
21
- "Dir.class_eval { `echo Hello` }",
22
- "FileTest.class_eval { `echo Hello` }",
23
- "File.eval \"`echo Hello`\"",
24
- "FileUtils.eval \"`echo Hello`\"",
25
- "Dir.eval \"`echo Hello`\"",
26
- "FileTest.eval \"`echo Hello`\"",
27
- "File.instance_eval { `echo Hello` }",
28
- "FileUtils.instance_eval { `echo Hello` }",
29
- "Dir.instance_eval { `echo Hello` }",
30
- "FileTest.instance_eval { `echo Hello` }",
31
- "f=IO.popen('uname'); f.readlines; f.close",
32
- "IO.binread('/etc/passwd')",
33
- "IO.read('/etc/passwd')",
34
- ]
38
+ it 'returns correct object' do
39
+ expect(described_class.eval('[1,2,3]')).to(eq([1, 2, 3]))
40
+ end
35
41
 
36
42
  MALICIOUS_OPERATIONS.each do |op|
37
43
  it "protects from malicious operations like (#{op})" do
38
- expect{
39
- SafeRuby.eval(op)
40
- }.to raise_error RuntimeError
44
+ expect { described_class.eval(op) }
45
+ .to(raise_error(RuntimeError))
41
46
  end
42
47
  end
43
48
 
44
- describe "options" do
45
- describe "timeout" do
49
+ describe 'options' do
50
+ describe 'timeout' do
46
51
  it 'defaults to a 5 second timeout' do
47
- time = Benchmark.realtime do
48
- SafeRuby.eval('(1..100000).map {|n| n**100}')
49
- end
50
- expect(time).to be_within(0.5).of(5)
52
+ time =
53
+ Benchmark.realtime do
54
+ described_class.eval('(1..100000).map {|n| n**100}')
55
+ end
56
+ expect(time).to(be_within(0.5).of(5))
51
57
  end
52
58
 
53
59
  it 'allows custom timeout' do
54
- time = Benchmark.realtime do
55
- SafeRuby.eval('(1..100000).map {|n| n**100}', timeout: 1)
56
- end
57
- expect(time).to be_within(0.5).of(1)
60
+ time =
61
+ Benchmark.realtime do
62
+ described_class.eval('(1..100000).map {|n| n**100}', timeout: 1)
63
+ end
64
+ expect(time).to(be_within(0.5).of(1))
58
65
  end
59
66
  end
60
67
 
61
- describe "raising errors" do
62
- it "defaults to raising errors" do
63
- expect{ SafeRuby.eval("asdasdasd") }.to raise_error RuntimeError
68
+ describe 'raising errors' do
69
+ it 'defaults to raising errors' do
70
+ expect { described_class.eval('asdasdasd') }
71
+ .to(raise_error(RuntimeError))
64
72
  end
65
73
 
66
- it "allows not raising errors" do
67
- expect {SafeRuby.eval("asdasd", raise_errors: false)}.to_not raise_error
74
+ it 'allows not raising errors' do
75
+ expect { described_class.eval('asdasd', raise_errors: false) }
76
+ .not_to(raise_error)
68
77
  end
69
78
  end
70
79
  end
71
-
72
80
  end
73
81
  end
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,12 @@
1
- require 'safe_ruby'
1
+ # Copyright (c) 2018 Uku Taht
2
+ # frozen_string_literal: true
3
+
2
4
  require 'benchmark'
5
+ require 'safe_ruby'
3
6
 
4
7
  RSpec.configure do |config|
5
8
  config.run_all_when_everything_filtered = true
6
- config.filter_run :focus
9
+ config.filter_run(:focus)
7
10
 
8
11
  config.order = 'random'
9
12
  end
@@ -0,0 +1,8 @@
1
+ # Copyright (c) 2023 Jerome Arbez-Gindre
2
+ # frozen_string_literal: true
3
+
4
+ desc 'Starts the interactive console'
5
+ task :console do
6
+ require 'pry'
7
+ Pry.start
8
+ end
@@ -0,0 +1,4 @@
1
+ # Copyright (c) 2023 Jerome Arbez-Gindre
2
+ # frozen_string_literal: true
3
+
4
+ require('bundler/gem_tasks')
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2023 Jerome Arbez-Gindre
2
+ # frozen_string_literal: true
3
+
4
+ namespace 'quality' do
5
+ begin
6
+ require('rubocop/rake_task')
7
+
8
+ RuboCop::RakeTask.new do |task|
9
+ task.options << '--display-cop-names'
10
+ task.options << '--config=config/rubocop.yml'
11
+ end
12
+ rescue LoadError
13
+ task(:rubocop) do
14
+ puts('Install rubocop to run its rake tasks')
15
+ end
16
+ end
17
+
18
+ desc 'Runs all quality code check'
19
+ task(all: ['quality:rubocop'])
20
+ end
21
+
22
+ desc 'Synonym for quality:rubocop'
23
+ task(rubocop: 'quality:rubocop')
data/tasks/test.rake ADDED
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2023 Jerome Arbez-Gindre
2
+ # frozen_string_literal: true
3
+
4
+ require('rspec/core/rake_task')
5
+
6
+ namespace 'test' do
7
+ RSpec::Core::RakeTask.new(:spec) do |t|
8
+ t.rspec_opts = ['--options config/rspec']
9
+ end
10
+
11
+ desc 'Runs all unit tests and acceptance tests'
12
+ task(all: ['test:spec'])
13
+ end
14
+
15
+ desc 'Synonym for test:spec'
16
+ task(spec: 'test:spec')
17
+
18
+ desc 'Synonym for test:all'
19
+ task(test: 'test:all')