shikashi 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/README +5 -8
- data/Rakefile +2 -2
- data/examples/benchmark/bm1.rb +29 -0
- data/examples/benchmark/bm2.rb +25 -0
- data/lib/shikashi/sandbox.rb +121 -31
- metadata +9 -7
data/CHANGELOG
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
0.5.0 Refactored sandbox to keep base namespace on each run (see base_namespace example on README)
|
2
|
+
|
3
|
+
Code packet feature (see documentation)
|
4
|
+
|
5
|
+
Fixed spec to make it compatible with Ruby 1.9
|
6
|
+
|
1
7
|
0.4.0 Removed evalmimic dependency
|
2
8
|
|
3
9
|
Implemented sugar syntax for Sandbox and Privileges methods (see documentation)
|
data/README
CHANGED
@@ -167,9 +167,6 @@ define a class from inside the sandbox and use it from outside
|
|
167
167
|
|
168
168
|
include Shikashi
|
169
169
|
|
170
|
-
module SandboxModule
|
171
|
-
end
|
172
|
-
|
173
170
|
class X
|
174
171
|
def foo
|
175
172
|
print "X#foo\n"
|
@@ -177,23 +174,23 @@ define a class from inside the sandbox and use it from outside
|
|
177
174
|
end
|
178
175
|
|
179
176
|
s = Sandbox.new
|
180
|
-
priv = Privileges.new
|
181
|
-
priv.allow_method :print
|
182
177
|
|
183
178
|
s.run( "
|
184
|
-
class
|
179
|
+
class X
|
185
180
|
def foo
|
186
181
|
print \"foo defined inside the sandbox\\n\"
|
187
182
|
end
|
188
183
|
end
|
189
|
-
",
|
184
|
+
", Privileges.allow_method(:print))
|
190
185
|
|
191
186
|
|
192
187
|
x = X.new # X class is not affected by the sandbox (The X Class defined in the sandbox is SandboxModule::X)
|
193
188
|
x.foo
|
194
189
|
|
195
|
-
x =
|
190
|
+
x = s.base_namespace::X.new
|
196
191
|
x.foo
|
192
|
+
|
193
|
+
s.run("X.new.foo", Privileges.allow_method(:new).allow_method(:foo))
|
197
194
|
|
198
195
|
|
199
196
|
|
data/Rakefile
CHANGED
@@ -6,13 +6,13 @@ require 'rake/gempackagetask'
|
|
6
6
|
|
7
7
|
spec = Gem::Specification.new do |s|
|
8
8
|
s.name = 'shikashi'
|
9
|
-
s.version = '0.
|
9
|
+
s.version = '0.5.0'
|
10
10
|
s.author = 'Dario Seminara'
|
11
11
|
s.email = 'robertodarioseminara@gmail.com'
|
12
12
|
s.platform = Gem::Platform::RUBY
|
13
13
|
s.summary = 'shikashi is a ruby sandbox that permits the execution of "unprivileged" scripts by defining the permitted methods and constants the scripts can invoke with a white list logic'
|
14
14
|
s.homepage = "http://github.com/tario/shikashi"
|
15
|
-
s.add_dependency "evalhook", ">= 0.
|
15
|
+
s.add_dependency "evalhook", ">= 0.5.0"
|
16
16
|
s.add_dependency "getsource", ">= 0.1.0"
|
17
17
|
s.has_rdoc = true
|
18
18
|
s.extra_rdoc_files = [ 'README' ]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "shikashi"
|
3
|
+
require "benchmark"
|
4
|
+
|
5
|
+
code = "class X
|
6
|
+
def foo(n)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
X.new.foo(1000)
|
10
|
+
"
|
11
|
+
|
12
|
+
s = Shikashi::Sandbox.new
|
13
|
+
|
14
|
+
Benchmark.bm(7) do |x|
|
15
|
+
|
16
|
+
x.report("normal") {
|
17
|
+
1000.times do
|
18
|
+
s.run(code, Shikashi::Privileges.allow_method(:new))
|
19
|
+
end
|
20
|
+
}
|
21
|
+
|
22
|
+
x.report("packet") {
|
23
|
+
packet = s.packet(code, Shikashi::Privileges.allow_method(:new))
|
24
|
+
1000.times do
|
25
|
+
packet.run
|
26
|
+
end
|
27
|
+
}
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "shikashi"
|
3
|
+
require "benchmark"
|
4
|
+
|
5
|
+
s = Shikashi::Sandbox.new
|
6
|
+
|
7
|
+
class NilClass
|
8
|
+
def foo
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
Benchmark.bm(7) do |x|
|
13
|
+
|
14
|
+
x.report {
|
15
|
+
|
16
|
+
code = "
|
17
|
+
500000.times {
|
18
|
+
nil.foo
|
19
|
+
}
|
20
|
+
"
|
21
|
+
|
22
|
+
s.run code, Shikashi::Privileges.allow_method(:times).allow_method(:foo)
|
23
|
+
}
|
24
|
+
|
25
|
+
end
|
data/lib/shikashi/sandbox.rb
CHANGED
@@ -87,6 +87,10 @@ module Shikashi
|
|
87
87
|
def initialize
|
88
88
|
@privileges = Hash.new
|
89
89
|
@chain = Hash.new
|
90
|
+
@hook_handler = EvalhookHandler.new
|
91
|
+
@hook_handler.sandbox = self
|
92
|
+
@base_namespace = create_adhoc_base_namespace
|
93
|
+
@hook_handler.base_namespace = @base_namespace
|
90
94
|
end
|
91
95
|
|
92
96
|
# add a chain of sources, used internally
|
@@ -98,6 +102,38 @@ module Shikashi
|
|
98
102
|
@base_namespace
|
99
103
|
end
|
100
104
|
|
105
|
+
class Packet
|
106
|
+
def initialize(evalhook_packet, default_privileges, source) #:nodoc:
|
107
|
+
@evalhook_packet = evalhook_packet
|
108
|
+
@default_privileges = default_privileges
|
109
|
+
@source = source
|
110
|
+
end
|
111
|
+
|
112
|
+
#Run the code in the package
|
113
|
+
#
|
114
|
+
#call-seq: run(arguments)
|
115
|
+
#
|
116
|
+
#Arguments
|
117
|
+
#
|
118
|
+
# :binding Optional argument with the binding object of the context where the code is to be executed
|
119
|
+
# The default is a binding in the global context
|
120
|
+
# :timeout Optional argument to restrict the execution time of the script to a given value in seconds
|
121
|
+
def run(*args)
|
122
|
+
t = args.pick(:timeout) do nil end
|
123
|
+
binding_ = args.pick(Binding,:binding) do
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
|
127
|
+
begin
|
128
|
+
timeout t do
|
129
|
+
@evalhook_packet.run(binding_, @source, 0)
|
130
|
+
end
|
131
|
+
rescue ::Timeout::Error
|
132
|
+
raise Shikashi::Timeout::Error
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
101
137
|
class EvalhookHandler < EvalHook::HookHandler
|
102
138
|
attr_accessor :sandbox
|
103
139
|
|
@@ -143,7 +179,8 @@ module Shikashi
|
|
143
179
|
source = get_caller
|
144
180
|
privileges = sandbox.privileges[source]
|
145
181
|
if privileges
|
146
|
-
|
182
|
+
constants = sandbox.base_namespace.constants
|
183
|
+
unless constants.include? name or constants.include? name.to_sym
|
147
184
|
unless privileges.const_read_allowed? name.to_s
|
148
185
|
raise SecurityError, "cannot access constant #{name}"
|
149
186
|
end
|
@@ -226,13 +263,10 @@ module Shikashi
|
|
226
263
|
end
|
227
264
|
end # Class
|
228
265
|
|
229
|
-
#Run the code in sandbox with the given privileges
|
230
|
-
#execution of classes and methods defined in the sandbox from outside the sandbox if a block is passed
|
266
|
+
#Run the code in sandbox with the given privileges
|
231
267
|
# (see examples)
|
232
268
|
#
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#Arguments
|
269
|
+
# Arguments
|
236
270
|
#
|
237
271
|
# :code Mandatory argument of class String with the code to execute restricted in the sandbox
|
238
272
|
# :privileges Optional argument of class Shikashi::Sandbox::Privileges to indicate the restrictions of the
|
@@ -244,6 +278,9 @@ module Shikashi
|
|
244
278
|
# be specified as hash parameter
|
245
279
|
# :timeout Optional argument to restrict the execution time of the script to a given value in seconds
|
246
280
|
# (accepts integer and decimal values), when timeout hits Shikashi::Timeout::Error is raised
|
281
|
+
# :base_namespace Alternate module to contain all classes and constants defined by the unprivileged code
|
282
|
+
# if not specified, by default, the base_namespace is created with the sandbox itself
|
283
|
+
# :no_base_namespace Specify to do not use a base_namespace (default false, not recommended to change)
|
247
284
|
#
|
248
285
|
#
|
249
286
|
#The arguments can be passed in any order and using hash notation or not, examples:
|
@@ -286,29 +323,77 @@ module Shikashi
|
|
286
323
|
# end
|
287
324
|
# ', :privileges => privileges)
|
288
325
|
#
|
289
|
-
# #outside of this block, the method foo defined in the sandbox are invisible
|
290
|
-
# sandbox.run do
|
291
|
-
# self.foo
|
292
|
-
# end
|
293
|
-
#
|
294
326
|
#
|
295
327
|
def run(*args)
|
296
|
-
|
328
|
+
run_i(*args)
|
329
|
+
end
|
297
330
|
|
298
|
-
|
299
|
-
|
331
|
+
#Creates a packet of code with the given privileges to execute later as many times as neccessary
|
332
|
+
#
|
333
|
+
# (see examples)
|
334
|
+
#
|
335
|
+
# Arguments
|
336
|
+
#
|
337
|
+
# :code Mandatory argument of class String with the code to execute restricted in the sandbox
|
338
|
+
# :privileges Optional argument of class Shikashi::Sandbox::Privileges to indicate the restrictions of the
|
339
|
+
# code executed in the sandbox. The default is an empty Privileges (absolutly no permission)
|
340
|
+
# Must be of class Privileges or passed as hash_key (:privileges => privileges)
|
341
|
+
# :source Optional argument to indicate the "source name", (visible in the backtraces). Only can
|
342
|
+
# be specified as hash parameter
|
343
|
+
# :base_namespace Alternate module to contain all classes and constants defined by the unprivileged code
|
344
|
+
# if not specified, by default, the base_namespace is created with the sandbox itself
|
345
|
+
# :no_base_namespace Specify to do not use a base_namespace (default false, not recommended to change)
|
346
|
+
#
|
347
|
+
# NOTE: arguments are the same as for Sandbox#run method, except for timeout and binding which can be
|
348
|
+
# used when calling Shikashi::Sandbox::Packet#run
|
349
|
+
#
|
350
|
+
#Example:
|
351
|
+
#
|
352
|
+
# require "rubygems"
|
353
|
+
# require "shikashi"
|
354
|
+
#
|
355
|
+
# include Shikashi
|
356
|
+
#
|
357
|
+
# sandbox = Sandbox.new
|
358
|
+
#
|
359
|
+
# privileges = Privileges.allow_method(:print)
|
360
|
+
#
|
361
|
+
# # this is equivallent to sandbox.run('print "hello world\n"')
|
362
|
+
# packet = sandbox.packet('print "hello world\n"', privileges)
|
363
|
+
# packet.run
|
364
|
+
#
|
365
|
+
def packet(*args)
|
300
366
|
code = args.pick(String,:code)
|
367
|
+
base_namespace = args.pick(:base_namespace) do nil end
|
368
|
+
no_base_namespace = args.pick(:no_base_namespace) do @no_base_namespace end
|
369
|
+
privileges_ = args.pick(Privileges,:privileges) do Privileges.new end
|
301
370
|
|
302
|
-
|
303
|
-
base_namespace = args.pick(:base_namespace) do create_adhoc_base_namespace end
|
371
|
+
hook_handler = nil
|
304
372
|
|
305
|
-
|
306
|
-
|
373
|
+
if base_namespace
|
374
|
+
hook_handler = EvalhookHandler.new
|
375
|
+
hook_handler.base_namespace = base_namespace
|
376
|
+
hook_handler.sandbox = self
|
377
|
+
else
|
378
|
+
hook_handler = @hook_handler
|
379
|
+
base_namespace = hook_handler.base_namespace
|
380
|
+
end
|
381
|
+
source = args.pick(:source) do generate_id end
|
382
|
+
|
383
|
+
self.privileges[source] = privileges_
|
384
|
+
|
385
|
+
code = "nil;\n " + code
|
386
|
+
|
387
|
+
unless no_base_namespace
|
388
|
+
if (eval(base_namespace.to_s).instance_of? Module)
|
389
|
+
code = "module #{base_namespace}\n #{code}\n end\n"
|
390
|
+
else
|
391
|
+
code = "class #{base_namespace}\n #{code}\n end\n"
|
392
|
+
end
|
307
393
|
end
|
308
|
-
@base_namespace = base_namespace
|
309
|
-
no_base_namespace = args.pick(:no_base_namespace) do false end
|
310
394
|
|
311
|
-
|
395
|
+
evalhook_packet = @hook_handler.packet(code)
|
396
|
+
Shikashi::Sandbox::Packet.new(evalhook_packet, privileges_, source)
|
312
397
|
end
|
313
398
|
|
314
399
|
def create_hook_handler(*args)
|
@@ -338,7 +423,6 @@ private
|
|
338
423
|
|
339
424
|
def run_i(*args)
|
340
425
|
|
341
|
-
|
342
426
|
t = args.pick(:timeout) do nil end
|
343
427
|
raise Shikashi::Timeout::Error if t == 0
|
344
428
|
t = t || 0
|
@@ -352,26 +436,32 @@ private
|
|
352
436
|
code = args.pick(String,:code)
|
353
437
|
binding_ = args.pick(Binding,:binding) do Shikashi.global_binding end
|
354
438
|
source = args.pick(:source) do generate_id end
|
355
|
-
base_namespace = args.pick(:base_namespace) do
|
356
|
-
no_base_namespace = args.pick(:no_base_namespace) do
|
439
|
+
base_namespace = args.pick(:base_namespace) do nil end
|
440
|
+
no_base_namespace = args.pick(:no_base_namespace) do @no_base_namespace end
|
357
441
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
442
|
+
hook_handler = nil
|
443
|
+
|
444
|
+
if base_namespace
|
445
|
+
hook_handler = EvalhookHandler.new
|
446
|
+
hook_handler.base_namespace = base_namespace
|
447
|
+
hook_handler.sandbox = self
|
448
|
+
else
|
449
|
+
hook_handler = @hook_handler
|
450
|
+
base_namespace = hook_handler.base_namespace
|
451
|
+
end
|
363
452
|
|
453
|
+
self.privileges[source] = privileges_
|
364
454
|
code = "nil;\n " + code
|
365
455
|
|
366
456
|
unless no_base_namespace
|
367
|
-
if (base_namespace.instance_of? Module)
|
457
|
+
if (eval(base_namespace.to_s).instance_of? Module)
|
368
458
|
code = "module #{base_namespace}\n #{code}\n end\n"
|
369
459
|
else
|
370
460
|
code = "class #{base_namespace}\n #{code}\n end\n"
|
371
461
|
end
|
372
462
|
end
|
373
463
|
|
374
|
-
|
464
|
+
hook_handler.evalhook(code, binding_, source)
|
375
465
|
end
|
376
466
|
rescue ::Timeout::Error
|
377
467
|
raise Shikashi::Timeout::Error
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shikashi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 5
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.5.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Dario Seminara
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-06-20 00:00:00 -03:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -26,12 +26,12 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
hash:
|
29
|
+
hash: 11
|
30
30
|
segments:
|
31
31
|
- 0
|
32
|
-
-
|
32
|
+
- 5
|
33
33
|
- 0
|
34
|
-
version: 0.
|
34
|
+
version: 0.5.0
|
35
35
|
type: :runtime
|
36
36
|
version_requirements: *id001
|
37
37
|
- !ruby/object:Gem::Dependency
|
@@ -59,6 +59,8 @@ extensions: []
|
|
59
59
|
extra_rdoc_files:
|
60
60
|
- README
|
61
61
|
files:
|
62
|
+
- examples/benchmark/bm1.rb
|
63
|
+
- examples/benchmark/bm2.rb
|
62
64
|
- examples/basic/example1.rb
|
63
65
|
- examples/basic/example3.rb
|
64
66
|
- examples/basic/example5.rb
|