shikashi 0.1.2 → 0.2.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.
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