shikashi 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ 0.2.0 Added control to the access to constants and global variables (See documentation)
2
+
3
+ Remove rallhook dependency, replaced by getsource
4
+
5
+ Portability and stability improved by using evalhook to hook the method calls in the place of rallhook
6
+
7
+ Added timeout restriction to the scripts executed in the sandbox (default unlimited)
8
+
1
9
  0.1.2 Fixed compatibility issues with ruby1.9
2
10
 
3
11
  0.1.1 Fixed issue 2: example2 failed when run under ruby1.9 (http://github.com/tario/shikashi/issues#issue/2)
data/README CHANGED
@@ -4,19 +4,13 @@ Shikashi is an sandbox for ruby that handles all ruby method calls executed in t
4
4
  these calls depending on the receiver object, the method name, the source file from where the call was originated
5
5
  and the source file where the called method is implemented.
6
6
 
7
- The permissions for each sandboxed run is fully configurable and the implementation of the methods called from
8
- the sandbox can be replaced transparently (i.e: replace method IO#write in the sandbox context to capture the
9
- standard output of the script)
7
+ The permissions for each sandboxed run is fully configurable and the implementation of the methods called from within
8
+ the sandbox can be replaced transparently
10
9
 
11
- The implementation of shikashi is pure ruby and it is based on rallhook, an amending of the ruby interpreter
12
- (see http://tario.github.com/rallhook/doc)
10
+ The implementation of shikashi is written in pure ruby and now implemented based in evalhook, (see http://tario.github.com/evalhook)
13
11
 
14
12
  == Installation
15
13
 
16
- === Prerequisites
17
-
18
- * rallhook >= 0.7.0 (found at http://github.com/tario/rallhook)
19
-
20
14
  === Gem installation
21
15
 
22
16
  Run in the terminal:
@@ -203,3 +197,19 @@ Simple redirection of method, the method foo is replaced by TestWrapper::call
203
197
 
204
198
  s.run(perm,"X.new.foo")
205
199
 
200
+ === Timeout example
201
+
202
+ require "rubygems"
203
+ require "shikashi"
204
+
205
+ s = Shikashi::Sandbox.new
206
+ perm = Shikashi::Privileges.new
207
+
208
+ perm.allow_method :sleep
209
+
210
+ s.run(perm,"sleep 3", :timeout => 2) # raise Shikashi::Timeout::Error after 2 seconds
211
+
212
+
213
+ == Copying
214
+
215
+ Copyright (c) 2010 Dario Seminara, released under the GPL License (see LICENSE)
data/Rakefile CHANGED
@@ -6,13 +6,14 @@ require 'rake/gempackagetask'
6
6
 
7
7
  spec = Gem::Specification.new do |s|
8
8
  s.name = 'shikashi'
9
- s.version = '0.1.2'
9
+ s.version = '0.2.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 (I.E., the script cannot use the File class or a RoR Model Class unless that permission is specified) "well done version" of ruby-arena-sanbox based on rallhook'
14
14
  s.homepage = "http://github.com/tario/shikashi"
15
- s.add_dependency "rallhook", ">= 0.7.0"
15
+ s.add_dependency "evalhook", ">= 0.1.0"
16
+ s.add_dependency "getsource", ">= 0.1.0"
16
17
  s.has_rdoc = true
17
18
  s.extra_rdoc_files = [ 'README' ]
18
19
  # s.rdoc_options << '--main' << 'README'
@@ -25,7 +26,7 @@ task :default => [ :test ]
25
26
 
26
27
  Rake::TestTask.new('test') do |t|
27
28
  t.libs << 'test'
28
- t.pattern = '{test}/test_*.rb'
29
+ t.pattern = '{test}/**/test_*.rb'
29
30
  t.verbose = true
30
31
  end
31
32
 
data/TODO CHANGED
@@ -0,0 +1,2 @@
1
+
2
+ * Remove the rallhook gem dependency (is currentily used to read the source file of the called methods)
@@ -0,0 +1,19 @@
1
+ # "hello world" from within the sandbox
2
+
3
+ require "rubygems"
4
+ require "shikashi"
5
+
6
+ include Shikashi
7
+
8
+ s = Sandbox.new
9
+ priv = Privileges.new
10
+
11
+ priv.allow_method :print
12
+ priv.allow_global :$a
13
+
14
+ s.run(priv, '
15
+ $a = 9
16
+ print "assigned 9 to $a\n"
17
+ ')
18
+
19
+ p $a
@@ -0,0 +1,19 @@
1
+ # "hello world" from within the sandbox
2
+
3
+ require "rubygems"
4
+ require "shikashi"
5
+
6
+ include Shikashi
7
+
8
+ s = Sandbox.new
9
+ priv = Privileges.new
10
+
11
+ priv.allow_method :print
12
+ priv.allow_const "Object::A"
13
+
14
+ s.run(priv, '
15
+ print "assigned 8 to Object::A\n"
16
+ A = 8
17
+ ')
18
+ p A
19
+
@@ -0,0 +1,26 @@
1
+ require "rubygems"
2
+ require "shikashi"
3
+
4
+ class TestWrapper < Shikashi::Sandbox::MethodWrapper
5
+ def call(*args)
6
+ print "called foo from source: #{source}, arguments: #{args.inspect} \n"
7
+ original_call(*args)
8
+ end
9
+ end
10
+
11
+ class X
12
+ def foo
13
+ print "original foo\n"
14
+ end
15
+ end
16
+
17
+ s = Shikashi::Sandbox.new
18
+ perm = Shikashi::Privileges.new
19
+
20
+ perm.object(X).allow :new
21
+ perm.instances_of(X).allow :foo
22
+
23
+ # redirect calls to foo to TestWrapper
24
+ perm.instances_of(X).redirect :foo, TestWrapper
25
+
26
+ s.run(perm,"X.new.foo")
@@ -0,0 +1,40 @@
1
+ require "rubygems"
2
+ require "shikashi"
3
+
4
+ class TestWrapper < Shikashi::Sandbox::MethodWrapper
5
+ def call(*args)
6
+ print "called #{klass}#each block_given?:#{block_given?}, source: #{source}\n"
7
+ if block_given?
8
+ original_call(*args) do |*x|
9
+ print "yielded value #{x.first}\n"
10
+ yield(*x)
11
+ end
12
+ else
13
+ original_call(*args)
14
+ end
15
+ end
16
+ end
17
+
18
+ s = Shikashi::Sandbox.new
19
+ perm = Shikashi::Privileges.new
20
+
21
+ perm.instances_of(Array).allow :each
22
+ perm.instances_of(Array).redirect :each, TestWrapper
23
+
24
+ perm.instances_of(Enumerable::Enumerator).allow :each
25
+ perm.instances_of(Enumerable::Enumerator).redirect :each, TestWrapper
26
+
27
+ perm.allow_method :print
28
+
29
+ s.run perm, '
30
+ array = [1,2,3]
31
+
32
+ array.each do |x|
33
+ print x,"\n"
34
+ end
35
+
36
+ enum = array.each
37
+ enum.each do |x|
38
+ print x,"\n"
39
+ end
40
+ '
@@ -0,0 +1,9 @@
1
+ require "rubygems"
2
+ require "shikashi"
3
+
4
+ s = Shikashi::Sandbox.new
5
+ perm = Shikashi::Privileges.new
6
+
7
+ perm.allow_method :sleep
8
+
9
+ s.run(perm,"sleep 3", :timeout => 2) # raise Shikashi::Timeout::Error after 2 seconds
@@ -90,16 +90,16 @@ public
90
90
  @redirect_hash[method_name] = method_wrapper_class
91
91
  end
92
92
 
93
- def handle_redirection(klass, recv, method_name, method_id, sandbox)
93
+ def handle_redirection(klass, recv, method_name, sandbox)
94
94
  rclass = @redirect_hash[method_name.to_sym]
95
95
 
96
96
  if rclass
97
97
  if block_given?
98
- rclass.redirect_handler(klass, recv, method_name, method_id, sandbox) do |mh|
98
+ rclass.redirect_handler(klass, recv, method_name, 0, sandbox) do |mh|
99
99
  yield(mh)
100
100
  end
101
101
  else
102
- rclass.redirect_handler(klass, recv, method_name, method_id, sandbox)
102
+ rclass.redirect_handler(klass, recv, method_name, 0, sandbox)
103
103
  end
104
104
  else
105
105
  nil
@@ -114,6 +114,8 @@ public
114
114
  @allowed_instances = Hash.new
115
115
  @allowed_methods = Array.new
116
116
  @allowed_klass_methods = Hash.new
117
+ @allowed_globals = Array.new
118
+ @allowed_consts = Array.new
117
119
  end
118
120
 
119
121
  private
@@ -189,15 +191,14 @@ public
189
191
  @allowed_methods << method_name
190
192
  end
191
193
 
192
- def handle_redirection(klass, recv, method_name, method_id, sandbox)
194
+ def handle_redirection(klass, recv, method_name, sandbox)
193
195
  if @last_allowed
194
-
195
196
  if block_given?
196
- @last_allowed.handle_redirection(klass, recv, method_name, method_id, sandbox) do |mh|
197
+ @last_allowed.handle_redirection(klass, recv, method_name, sandbox) do |mh|
197
198
  yield(mh)
198
199
  end
199
200
  else
200
- @last_allowed.handle_redirection(klass, recv, method_name, method_id, sandbox)
201
+ @last_allowed.handle_redirection(klass, recv, method_name, sandbox)
201
202
  end
202
203
  else
203
204
  nil
@@ -208,7 +209,7 @@ public
208
209
  def allow?(klass, recv, method_name, method_id)
209
210
 
210
211
  m = nil
211
- m = klass.shadow.instance_method(method_name) if method_name
212
+ m = klass.instance_method(method_name) if method_name
212
213
 
213
214
  begin
214
215
  return true if @allowed_methods.include?(method_name)
@@ -284,6 +285,56 @@ public
284
285
  end
285
286
  end
286
287
 
288
+
289
+ def global_allowed?(varname)
290
+ @allowed_globals.include? varname
291
+ end
292
+
293
+ def const_allowed?(varname)
294
+ @allowed_consts.include? varname
295
+ end
296
+
297
+ # defines the permissions needed to create or change a global variable
298
+ #
299
+ # Example:
300
+ #
301
+ # s = Sandbox.new
302
+ # priv = Privileges.new
303
+ #
304
+ # priv.allow_method :print
305
+ # priv.allow_global :$a
306
+ #
307
+ # s.run(priv, '
308
+ # $a = 9
309
+ # print "assigned 9 to $a\n"
310
+ # ')
311
+ #
312
+ # p $a
313
+ #
314
+ def allow_global( varname )
315
+ @allowed_globals << varname
316
+ end
317
+
318
+ # defines the permissions needed to create or change a const
319
+ #
320
+ # Example:
321
+ # s = Sandbox.new
322
+ # priv = Privileges.new
323
+ #
324
+ # priv.allow_method :print
325
+ # priv.allow_const "Object::A"
326
+ #
327
+ # s.run(priv, '
328
+ # print "assigned 8 to Object::A\n"
329
+ # A = 8
330
+ # ')
331
+ #
332
+ # p A
333
+
334
+ def allow_const( varname )
335
+ @allowed_consts << varname
336
+ end
337
+
287
338
  end
288
339
 
289
340
  end
@@ -18,10 +18,12 @@ you should have received a copy of the gnu general public license
18
18
  along with shikashi. if not, see <http://www.gnu.org/licenses/>.
19
19
 
20
20
  =end
21
-
22
- require "rallhook"
21
+ require "rubygems"
22
+ require "evalhook"
23
23
  require "shikashi/privileges"
24
24
  require "shikashi/pick_argument"
25
+ require "getsource"
26
+ require "timeout"
25
27
 
26
28
  module Shikashi
27
29
 
@@ -29,6 +31,14 @@ module Shikashi
29
31
  attr_accessor :global_binding
30
32
  end
31
33
 
34
+ module Timeout
35
+
36
+
37
+ #raised when reach the timeout in a script execution restricted by timeout (see Sandbox#run)
38
+ class Error < Exception
39
+
40
+ end
41
+ end
32
42
 
33
43
  #The sandbox class run the sandbox, because of internal behaviour only can be use one instance
34
44
  #of sandbox by thread (each different thread may have its own sandbox running in the same time)
@@ -119,9 +129,10 @@ module Shikashi
119
129
  #
120
130
  # class TestWrapper < Shikashi::Sandbox::MethodWrapper
121
131
  # def call(*args)
122
- # print "called each from source: #{source}\n"
132
+ # print "called #{klass}#each block_given?:#{block_given?}, source: #{source}\n"
123
133
  # if block_given?
124
134
  # original_call(*args) do |*x|
135
+ # print "yielded value #{x.first}\n"
125
136
  # yield(*x)
126
137
  # end
127
138
  # else
@@ -132,8 +143,13 @@ module Shikashi
132
143
  #
133
144
  # s = Shikashi::Sandbox.new
134
145
  # perm = Shikashi::Privileges.new
146
+ #
135
147
  # perm.instances_of(Array).allow :each
148
+ # perm.instances_of(Array).redirect :each, TestWrapper
149
+ #
136
150
  # perm.instances_of(Enumerable::Enumerator).allow :each
151
+ # perm.instances_of(Enumerable::Enumerator).redirect :each, TestWrapper
152
+ #
137
153
  # perm.allow_method :print
138
154
  #
139
155
  # s.run perm, '
@@ -148,64 +164,100 @@ module Shikashi
148
164
  # print x,"\n"
149
165
  # end
150
166
  # '
151
- class MethodWrapper < RallHook::Helper::MethodWrapper
167
+ class MethodWrapper
168
+
169
+ class MethodRedirect
170
+ include RedirectHelper::MethodRedirect
171
+
172
+ attr_accessor :klass
173
+ attr_accessor :method_name
174
+ attr_accessor :recv
175
+ end
176
+
177
+ attr_accessor :recv
178
+ attr_accessor :method_name
179
+ attr_accessor :klass
152
180
  attr_accessor :sandbox
153
181
  attr_accessor :privileges
154
182
  attr_accessor :source
155
183
 
156
184
  def self.redirect_handler(klass,recv,method_name,method_id,sandbox)
157
- wrap = self.new
158
- wrap.klass = klass
159
- wrap.recv = recv
160
- wrap.method_name = method_name
161
- wrap.method_id = method_id
162
- wrap.sandbox = sandbox
163
-
164
- if block_given?
165
- yield(wrap)
166
- end
185
+ mw = self.new
186
+ mw.klass = klass
187
+ mw.recv = recv
188
+ mw.method_name = method_name
189
+ mw.sandbox = sandbox
167
190
 
168
- return wrap.redirect_with_unhook(:call_with_rehook)
169
- end
170
- end
191
+ if block_given?
192
+ yield(mw)
193
+ end
194
+
195
+ mr = MethodRedirect.new
196
+
197
+ mr.recv = mw
198
+ mr.klass = mw.class
199
+ mr.method_name = :call
171
200
 
172
- # Used internally
173
- class InheritedWrapper < MethodWrapper
174
- def call(*args)
175
- subclass = args.first
176
- privileges.object(subclass).allow :new
177
- privileges.instances_of(subclass).allow :initialize
178
- original_call(*args)
201
+ mr
179
202
  end
180
- end
181
203
 
182
- # Used internally
183
- class DummyWrapper < MethodWrapper
184
- def call(*args)
204
+ def original_call(*args)
185
205
  if block_given?
186
- original_call(*args) do |*x|
206
+ klass.instance_method(method_name).bind(recv).call(*args) do |*x|
187
207
  yield(*x)
188
208
  end
189
209
  else
190
- original_call(*args)
210
+ klass.instance_method(method_name).bind(recv).call(*args)
191
211
  end
192
212
  end
193
213
  end
194
214
 
195
- class RallhookHandler < RallHook::HookHandler
215
+ class EvalhookHandler < EvalHook::HookHandler
196
216
  attr_accessor :sandbox
197
217
  attr_accessor :redirect
198
218
 
199
- def handle_method(klass, recv, method_name, method_id)
219
+ def handle_gasgn( global_id, value )
220
+ source = caller[1].split(":").first
221
+
222
+ privileges = sandbox.privileges[source]
223
+ if privileges
224
+ unless privileges.global_allowed? global_id
225
+ raise SecurityError.new("Cannot assign global variable #{global_id}")
226
+ end
227
+ end
228
+
229
+ nil
230
+ end
231
+
232
+ def handle_cdecl(klass, const_id, value)
233
+ source = caller[1].split(":").first
234
+
235
+ privileges = sandbox.privileges[source]
236
+ if privileges
237
+ unless privileges.const_allowed? "#{klass}::#{const_id}"
238
+ raise SecurityError.new("Cannot assign const #{klass}::#{const_id}")
239
+ end
240
+ end
241
+
242
+ nil
243
+
244
+
245
+ end
246
+
247
+ def handle_method(klass, recv, method_name)
200
248
  source = nil
249
+
250
+ method_id = 0
251
+
201
252
  if method_name
202
253
 
203
- source = caller.first.split(":").first
204
- dest_source = klass.shadow.instance_method(method_name).body.file
254
+ source = caller[1].split(":").first
255
+ dest_source = klass.instance_method(method_name).body.file
205
256
 
206
257
  privileges = nil
207
258
  if source != dest_source then
208
259
  privileges = sandbox.privileges[source]
260
+
209
261
  if privileges then
210
262
  privileges = privileges.dup
211
263
  loop_source = source
@@ -229,12 +281,6 @@ module Shikashi
229
281
  end
230
282
  end
231
283
 
232
- if method_name == :inherited and recv.instance_of? Class
233
- mw = InheritedWrapper.redirect_handler(klass,recv,method_name,method_id,sandbox)
234
- mw.recv.privileges = privileges
235
- return mw
236
- end
237
-
238
284
  return nil if method_name == :instance_eval
239
285
  return nil if method_name == :binding
240
286
 
@@ -245,20 +291,13 @@ module Shikashi
245
291
  end
246
292
  end
247
293
 
248
- if dest_source == ""
249
- return nil if (method_name.to_s == "core#define_method")
250
- return nil if (method_name.to_s == "core#define_singleton_method")
251
- return DummyWrapper.redirect_handler(klass,recv,method_name,method_id,sandbox)
252
- end
253
-
254
294
  end
255
295
 
256
-
257
296
  if privileges
258
- privileges.handle_redirection(klass,recv,method_name,method_id,sandbox) do |mh|
259
- mh.privileges = privileges
260
- mh.source = source
261
- end
297
+ privileges.handle_redirection(klass,recv,method_name,sandbox) do |mh|
298
+ mh.privileges = privileges
299
+ mh.source = source
300
+ end
262
301
  end
263
302
 
264
303
  end # if
@@ -280,6 +319,8 @@ module Shikashi
280
319
  # The default is a binding in the global context
281
320
  # :source Optional argument to indicate the "source name", (visible in the backtraces). Only can
282
321
  # be specified as hash parameter
322
+ # :timeout Optional argument to restrict the execution time of the script to a given value in seconds
323
+ # (accepts integer and decimal values), when timeout hits Shikashi::Timeout::Error is raised
283
324
  #
284
325
  #
285
326
  #The arguments can be passed in any order and using hash notation or not, examples:
@@ -331,25 +372,30 @@ module Shikashi
331
372
 
332
373
  def run(*args)
333
374
 
334
- handler = RallhookHandler.new
375
+ handler = EvalhookHandler.new
335
376
  handler.redirect = @redirect_hash
336
377
  handler.sandbox = self
337
378
 
379
+ t = args.pick(:timeout) do nil end
380
+ raise Shikashi::Timeout::Error if t == 0
381
+ t = t || 0
382
+
338
383
  if block_given?
339
- handler.hook do
340
- yield
341
- end
384
+ yield
342
385
  else
386
+ begin
387
+ timeout t do
388
+ privileges_ = args.pick(Privileges,:privileges) do Privileges.new end
389
+ code = args.pick(String,:code)
390
+ binding_ = args.pick(Binding,:binding) do Shikashi.global_binding end
391
+ source = args.pick(:source) do generate_id end
343
392
 
344
- privileges_ = args.pick(Privileges,:privileges) do Privileges.new end
345
- code = args.pick(String,:code)
346
- binding_ = args.pick(Binding,:binding) do Shikashi.global_binding end
347
- source = args.pick(:source) do generate_id end
348
-
349
- self.privileges[source] = privileges_
393
+ self.privileges[source] = privileges_
350
394
 
351
- handler.hook do
352
- eval(code, binding_, source)
395
+ handler.evalhook(code, binding_, source)
396
+ end
397
+ rescue ::Timeout::Error
398
+ raise Shikashi::Timeout::Error
353
399
  end
354
400
  end
355
401
  end
@@ -0,0 +1,36 @@
1
+ require "test/unit"
2
+ require "shikashi"
3
+
4
+ class TimeoutTest < Test::Unit::TestCase
5
+
6
+ def _test_timeout(execution_delay, timeout)
7
+ priv = Shikashi::Privileges.new
8
+ # allow the execution of the method sleep to emulate an execution delay
9
+ priv.allow_method :sleep
10
+
11
+ if execution_delay > timeout
12
+ assert_raise Shikashi::Timeout::Error do
13
+ # specify the timeout and the current binding to use the execution_delay parameter
14
+ Shikashi::Sandbox.new.run("sleep execution_delay", priv, binding, :timeout => timeout)
15
+ end
16
+ else
17
+ assert_nothing_raised do
18
+ Shikashi::Sandbox.new.run("sleep execution_delay", priv, binding, :timeout => timeout)
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.add_test(name, execution_delay, timeout)
24
+ define_method("test_"+name) do
25
+ _test_timeout(execution_delay, timeout)
26
+ end
27
+ end
28
+
29
+ add_test "basic",2,1
30
+ add_test "float",0.2,0.1
31
+ add_test "float_no_hit",0.1,0.2
32
+ add_test "zero", 1,0
33
+ add_test "zero_no_hit", 0,1
34
+
35
+ end
36
+
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: 31
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
8
  - 2
10
- version: 0.1.2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Dario Seminara
@@ -15,25 +15,41 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-07 00:00:00 -03:00
18
+ date: 2010-10-30 00:00:00 -03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
- name: rallhook
22
+ name: evalhook
23
23
  prerelease: false
24
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- hash: 3
29
+ hash: 27
30
30
  segments:
31
31
  - 0
32
- - 7
32
+ - 1
33
33
  - 0
34
- version: 0.7.0
34
+ version: 0.1.0
35
35
  type: :runtime
36
36
  version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: getsource
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 27
46
+ segments:
47
+ - 0
48
+ - 1
49
+ - 0
50
+ version: 0.1.0
51
+ type: :runtime
52
+ version_requirements: *id002
37
53
  description:
38
54
  email: robertodarioseminara@gmail.com
39
55
  executables: []
@@ -47,8 +63,13 @@ files:
47
63
  - examples/basic/example3.rb
48
64
  - examples/basic/example5.rb
49
65
  - examples/basic/example4.rb
66
+ - examples/basic/example6.rb
50
67
  - examples/basic/example.rb
68
+ - examples/basic/example7.rb
51
69
  - examples/basic/example2.rb
70
+ - examples/timeout/example1.rb
71
+ - examples/redir/example1.rb
72
+ - examples/redir/example2.rb
52
73
  - lib/shikashi.rb
53
74
  - lib/shikashi/pick_argument.rb
54
75
  - lib/shikashi/sandbox.rb
@@ -56,6 +77,7 @@ files:
56
77
  - lib/shikashi/privileges/singleton_methods.rb
57
78
  - lib/shikashi/privileges/classes.rb
58
79
  - lib/shikashi/privileges.rb
80
+ - test/functional/test_timeout.rb
59
81
  - AUTHORS
60
82
  - CHANGELOG
61
83
  - README