Skip to content

Commit b3488f1

Browse files
committed
added error handling, basic tests
1 parent 332e98d commit b3488f1

10 files changed

+102
-59
lines changed

.rspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
--color
2+
--format progress

Gemfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
source 'https://rubygems.org'
22

3-
gem 'childprocess'
3+
gem 'childprocess', '>= 0.3.9'
4+
gem 'rspec', '>= 2.14.1'

Gemfile.lock

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,20 @@ GEM
33
specs:
44
childprocess (0.3.9)
55
ffi (~> 1.0, >= 1.0.11)
6+
diff-lcs (1.2.4)
67
ffi (1.9.3)
8+
rspec (2.14.1)
9+
rspec-core (~> 2.14.0)
10+
rspec-expectations (~> 2.14.0)
11+
rspec-mocks (~> 2.14.0)
12+
rspec-core (2.14.7)
13+
rspec-expectations (2.14.4)
14+
diff-lcs (>= 1.1.3, < 2.0)
15+
rspec-mocks (2.14.4)
716

817
PLATFORMS
918
ruby
1019

1120
DEPENDENCIES
12-
childprocess
21+
childprocess (>= 0.3.9)
22+
rspec (>= 2.14.1)

lib/.DS_Store

0 Bytes
Binary file not shown.

lib/safe_ruby.rb

Lines changed: 4 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,9 @@
1+
require 'childprocess'
12
require_relative 'method_whitelist'
23
require_relative 'constant_whitelist'
34
require_relative 'make_safe_code'
4-
require 'childprocess'
5+
require_relative 'safe_ruby_runner'
56

67
class SafeRuby
7-
def initialize(code)
8-
@code = code
9-
end
10-
11-
def self.eval(code)
12-
new(code).eval
13-
end
14-
15-
def self.check(code, expected)
16-
eval(code) == eval(expected)
17-
end
18-
19-
def eval
20-
temp = build_tempfile
21-
read, write = IO.pipe
22-
# readerr, writeerr = IO.pipe
23-
ChildProcess.build("ruby", temp.path).tap do |process|
24-
process.io.stdout = write
25-
process.io.stderr = write
26-
process.start
27-
process.wait
28-
write.close
29-
end
30-
31-
data = read.read
32-
p data
33-
Marshal.load(data)
34-
# if data.empty?
35-
# readerr.read
36-
# else
37-
# Marshal.load(data)
38-
# end
39-
end
40-
private
41-
42-
def build_tempfile
43-
require 'tempfile'
44-
file = Tempfile.new('saferuby')
45-
file.write(MAKE_SAFE_CODE)
46-
file.write <<-STRING
47-
begin
48-
result = eval('#{@code}')
49-
$stdout.puts Marshal.dump(result)
50-
rescue => e
51-
$stderr.puts e
52-
end
53-
STRING
54-
file.rewind
55-
file
56-
end
57-
end
58-
59-
p SafeRuby.check('system("ls")', "[2,3,4]")
8+
VERSION = "0.0.1"
9+
end

lib/safe_ruby_runner.rb

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
class EvalError < StandardError
2+
def initialize(msg); super; end
3+
end
4+
5+
class SafeRuby
6+
def initialize(code)
7+
@code = code
8+
end
9+
10+
def self.eval(code)
11+
new(code).eval
12+
end
13+
14+
def self.check(code, expected)
15+
eval(code) == eval(expected)
16+
end
17+
18+
def eval
19+
temp = build_tempfile
20+
read, write = IO.pipe
21+
ChildProcess.build("ruby", temp.path).tap do |process|
22+
process.io.stdout = write
23+
process.io.stderr = write
24+
process.start
25+
process.wait
26+
write.close
27+
end
28+
29+
data = read.read
30+
begin
31+
Marshal.load(data)
32+
rescue => e
33+
raise EvalError.new(data)
34+
end
35+
end
36+
37+
private
38+
39+
def build_tempfile
40+
require 'tempfile'
41+
file = Tempfile.new('saferuby')
42+
file.write(MAKE_SAFE_CODE)
43+
file.write <<-STRING
44+
begin
45+
result = eval('#{@code}')
46+
print Marshal.dump(result)
47+
rescue => e
48+
print e.message
49+
end
50+
STRING
51+
file.rewind
52+
file
53+
end
54+
end

safe_ruby-0.0.1.gem

7 KB
Binary file not shown.

safe_ruby.gemspec

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
Gem::Specification.new do |s|
22
s.name = 'safe_ruby'
3-
s.version = '0.0.0'
3+
s.version = '0.0.1'
44
s.date = '2013-12-04'
55
s.summary = "Run untrusted ruby code in a safe environment"
6-
s.description = "Whatever"
6+
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."
77
s.authors = ["Uku Taht"]
88
s.email = '[email protected]'
9-
s.files = ["lib/safe_ruby.rb", "lib/constant_whitelist.rb", "lib/make_safe.rb", "lib/method_whitelist.rb"]
9+
s.files = ["lib/safe_ruby.rb", "lib/constant_whitelist.rb", "lib/make_safe_code.rb", "lib/method_whitelist.rb", "lib/safe_ruby_runner.rb"]
1010
s.homepage =
1111
'http://rubygems.org/gems/safe_ruby'
1212
s.license = 'MIT'
13+
s.add_runtime_dependency 'childprocess', '>= 0.3.9'
14+
s.add_development_dependency 'rspec', '>= 2.14.1'
1315
end

spec/safe_ruby_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
require 'spec_helper'
2+
3+
describe SafeRuby do
4+
describe '#eval' do
5+
it 'allows basic operations' do
6+
expect{ SafeRuby.eval("4 + 5") }.to_not raise_error
7+
expect{ SafeRuby.eval("[4, 5].map{|n| n+1}") }.to_not raise_error
8+
end
9+
10+
it 'does not allow malicious operations' do
11+
expect{ SafeRuby.eval("system('ls')") }.to raise_error
12+
expect{ SafeRuby.eval("`ls`") }.to raise_error
13+
expect{ SafeRuby.eval("Kernel.abort") }.to raise_error
14+
end
15+
end
16+
end

spec/spec_helper.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require 'safe_ruby'
2+
RSpec.configure do |config|
3+
config.treat_symbols_as_metadata_keys_with_true_values = true
4+
config.run_all_when_everything_filtered = true
5+
config.filter_run :focus
6+
7+
config.order = 'random'
8+
end

0 commit comments

Comments
 (0)