Parent

Namespace

Class Index [+]

Quicksearch

Rufus::TreeChecker

TreeChecker relies on ruby_parser to turns a piece of ruby code (a string) into a bunch of sexpression and then TreeChecker will check that sexpression tree and raise a Rufus::SecurityException if an excluded pattern is spotted.

The TreeChecker is meant to be useful for people writing DSLs directly in Ruby (not via their own parser) that want to check and prevent bad things from happening in this code.

  tc = Rufus::TreeChecker.new do
    exclude_fvcall :abort
    exclude_fvcall :exit, :exit!
  end

  tc.check("1 + 1; abort")               # will raise a SecurityError
  tc.check("puts (1..10).to_a.inspect")  # OK

featured exclusion methods

call / vcall / fcall ?

What the difference between those ? Well, here is how those various piece of code look like :

  "exit"          => [:vcall, :exit]
  "Kernel.exit"   => [:call, [:const, :Kernel], :exit]
  "Kernel::exit"  => [:call, [:const, :Kernel], :exit]
  "k.exit"        => [:call, [:vcall, :k], :exit]
  "exit -1"       => [:fcall, :exit, [:array, [:lit, -1]]]

Obviously :fcall could be labelled as “function call”, :call is a call on to some instance, while vcall might either be a variable dereference or a function call with no arguments.

low-level rules

higher level rules

Those rules take no arguments

a bit further

It’s possible to clone a TreeChecker and to add some more rules to it :

  tc0 = Rufus::TreeChecker.new do
    #
    # calls to eval, module_eval and instance_eval are not allowed
    #
    exclude_eval
  end

  tc1 = tc0.clone
  tc1.add_rules do
    #
    # calls to any method on File and FileUtils classes are not allowed
    #
    exclude_call_on File, FileUtils
  end

Constants

VERSION

Public Class Methods

new(&block) click to toggle source

initializes the TreeChecker, expects a block

     # File lib/rufus/treechecker.rb, line 142
142:     def initialize(&block)
143: 
144:       @root_set = RuleSet.new
145:       @set = RuleSet.new
146:       @current_set = @set
147: 
148:       add_rules(&block)
149:     end

Protected Class Methods

parse(rubycode) click to toggle source

A simple parse (relies on ruby_parser currently)

     # File lib/rufus/treechecker.rb, line 522
522:     def self.parse(rubycode)
523: 
524:       RubyParser.new.parse(rubycode).to_a
525:     end

Public Instance Methods

add_rules(&block) click to toggle source

Adds a set of checks (rules) to this treechecker. Returns self.

     # File lib/rufus/treechecker.rb, line 187
187:     def add_rules(&block)
188: 
189:       instance_eval(&block) if block
190: 
191:       self
192:     end
check(rubycode) click to toggle source

Performs the check on the given String of ruby code. Will raise a Rufus::SecurityError if there is something excluded by the rules specified at the initialization of the TreeChecker instance.

     # File lib/rufus/treechecker.rb, line 163
163:     def check(rubycode)
164: 
165:       sexp = parse(rubycode)
166: 
167:       #@root_checks.each do |meth, *args|
168:       #  send meth, sexp, args
169:       #end
170:       @root_set.check(sexp)
171: 
172:       do_check(sexp)
173:     end
clone() click to toggle source

Return a copy of this TreeChecker instance

     # File lib/rufus/treechecker.rb, line 177
177:     def clone
178: 
179:       tc = TreeChecker.new
180:       tc.instance_variable_set(:@root_set, @root_set.clone)
181:       tc.instance_variable_set(:@set, @set.clone)
182:       tc
183:     end
freeze() click to toggle source

Freezes the treechecker instance “in depth“

     # File lib/rufus/treechecker.rb, line 196
196:     def freeze
197:       super
198:       @root_set.freeze
199:       @set.freeze
200:     end
ptree(rubycode) click to toggle source

pretty-prints the sexp tree of the given rubycode

     # File lib/rufus/treechecker.rb, line 129
129:     def ptree(rubycode)
130:       puts stree(rubycode)
131:     end
stree(rubycode) click to toggle source

returns the pretty-printed string of the given rubycode (thanks ruby_parser).

     # File lib/rufus/treechecker.rb, line 136
136:     def stree(rubycode)
137:       "#{rubycode.inspect}\n =>\n#{parse(rubycode).inspect}"
138:     end
to_s() click to toggle source
     # File lib/rufus/treechecker.rb, line 151
151:     def to_s
152:       s = "#{self.class} (#{self.object_id})\n"
153:       s << "root_set :\n"
154:       s << @root_set.to_s
155:       s << "set :\n"
156:       s << @set.to_s
157:     end

Protected Instance Methods

at_root(&block) click to toggle source

Within the ‘at_root’ block, rules are added to the @root_checks, ie they are evaluated only for the toplevel (root) sexp.

     # File lib/rufus/treechecker.rb, line 316
316:     def at_root(&block)
317: 
318:       @current_set = @root_set
319:       add_rules(&block)
320:       @current_set = @set
321:     end
do_check(sexp) click to toggle source

The actual check method, check() is rather a bootstrap one...

     # File lib/rufus/treechecker.rb, line 502
502:     def do_check(sexp)
503: 
504:       @set.check(sexp)
505: 
506:       return unless sexp.is_a?(Array) # check over, seems fine...
507: 
508:       # check children
509: 
510:       sexp.each { |c| do_check c }
511:     end
do_exclude_pair(first, args) click to toggle source
     # File lib/rufus/treechecker.rb, line 490
490:     def do_exclude_pair (first, args)
491: 
492:       args, message = extract_message(args)
493:       args.each do |a|
494:         expand_class(a).each do |c|
495:           @current_set.exclude_pattern([ first, c ], message)
496:         end
497:       end
498:     end
exclude_access_to(*args) click to toggle source

prevents access (calling methods and rebinding) to a class (or a list of classes

     # File lib/rufus/treechecker.rb, line 408
408:     def exclude_access_to(*args)
409:       exclude_call_on *args
410:       exclude_rebinding *args
411:     end
exclude_alias() click to toggle source

Bans the usage of ‘alias’

     # File lib/rufus/treechecker.rb, line 460
460:     def exclude_alias
461: 
462:       @current_set.exclude_symbol(:alias, "'alias' is forbidden")
463:       @current_set.exclude_symbol(:alias_method, "'alias_method' is forbidden")
464:     end
exclude_backquotes() click to toggle source

Bans the use of backquotes

     # File lib/rufus/treechecker.rb, line 477
477:     def exclude_backquotes
478: 
479:       @current_set.exclude_symbol(:xstr, 'backquotes are forbidden')
480:     end
exclude_call_on(*args) click to toggle source
     # File lib/rufus/treechecker.rb, line 372
372:     def exclude_call_on(*args)
373:       do_exclude_pair(:call, args)
374:     end
exclude_call_to(*args) click to toggle source
     # File lib/rufus/treechecker.rb, line 376
376:     def exclude_call_to(*args)
377:       args, message = extract_message(args)
378:       args.each { |a| @current_set.exclude_pattern([ :call, :any, a], message) }
379:     end
exclude_class_tinkering(*args) click to toggle source

Bans the defintion and the [re]openening of classes

a list of exceptions (classes) can be passed. Subclassing those exceptions is permitted.

    exclude_class_tinkering :except => [ String, Array ]
     # File lib/rufus/treechecker.rb, line 427
427:     def exclude_class_tinkering (*args)
428: 
429:       @current_set.exclude_pattern(
430:         [ :sclass ], 'opening the metaclass of an instance is forbidden')
431: 
432:       Array(args.last[:except]).each { |e|
433:         expand_class(e).each do |c|
434:           @current_set.accept_pattern([ :class, :any, c ])
435:         end
436:       } if args.last.is_a?(Hash)
437: 
438:       @current_set.exclude_pattern(
439:         [ :class ], 'defining a class is forbidden')
440:     end
exclude_def() click to toggle source

Bans method definitions

     # File lib/rufus/treechecker.rb, line 415
415:     def exclude_def
416: 
417:       @current_set.exclude_symbol(:defn, 'method definitions are forbidden')
418:     end
exclude_eval() click to toggle source

Bans the use of ‘eval’, ‘module_eval’ and ‘instance_eval‘

     # File lib/rufus/treechecker.rb, line 468
468:     def exclude_eval
469: 
470:       exclude_call_to(:eval, 'eval() is forbidden')
471:       exclude_call_to(:module_eval, 'module_eval() is forbidden')
472:       exclude_call_to(:instance_eval, 'instance_eval() is forbidden')
473:     end
exclude_fcall(*args) click to toggle source
     # File lib/rufus/treechecker.rb, line 359
359:     def exclude_fcall(*args)
360:       do_exclude_pair(:fcall, args)
361:     end
exclude_fvcall(*args) click to toggle source
     # File lib/rufus/treechecker.rb, line 367
367:     def exclude_fvcall(*args)
368:       do_exclude_pair(:fcall, args)
369:       do_exclude_pair(:vcall, args)
370:     end
exclude_fvccall(*args) click to toggle source
     # File lib/rufus/treechecker.rb, line 381
381:     def exclude_fvccall(*args)
382:       exclude_fvcall(*args)
383:       exclude_call_to(*args)
384:     end
exclude_global_vars() click to toggle source

Bans referencing or setting the value of global variables

     # File lib/rufus/treechecker.rb, line 452
452:     def exclude_global_vars
453: 
454:       @current_set.exclude_symbol(:gvar, 'global vars are forbidden')
455:       @current_set.exclude_symbol(:gasgn, 'global vars are forbidden')
456:     end
exclude_head(head, message=nil) click to toggle source

Adds a rule that will forbid sexps that begin with the given head

    tc = TreeChecker.new do
      exclude_head [ :block ]
    end

    tc.check('a = 2')         # ok
    tc.check('a = 2; b = 5')  # will raise an error as it's a block
     # File lib/rufus/treechecker.rb, line 349
349:     def exclude_head(head, message=nil)
350: 
351:       @current_set.exclude_pattern(head, message)
352:     end
exclude_module_tinkering() click to toggle source

Bans the definition or the opening of modules

     # File lib/rufus/treechecker.rb, line 444
444:     def exclude_module_tinkering
445: 
446:       @current_set.exclude_symbol(
447:         :module, 'defining or opening a module is forbidden')
448:     end
exclude_raise() click to toggle source

Bans raise and throw

     # File lib/rufus/treechecker.rb, line 484
484:     def exclude_raise
485: 
486:       exclude_fvccall(:raise, 'raise is forbidden')
487:       exclude_fvccall(:throw, 'throw is forbidden')
488:     end
exclude_rebinding(*args) click to toggle source

This rule :

    exclude_rebinding Kernel

will raise a security error for those pieces of code :

    k = Kernel
    k = ::Kernel
     # File lib/rufus/treechecker.rb, line 395
395:     def exclude_rebinding(*args)
396:       args, message = extract_message(args)
397:       args.each do |a|
398:         expand_class(a).each do |c|
399:           @current_set.exclude_pattern([ :lasgn, :any, c], message)
400:         end
401:       end
402:     end
exclude_symbol(*args) click to toggle source
     # File lib/rufus/treechecker.rb, line 354
354:     def exclude_symbol(*args)
355:       args, message = extract_message(args)
356:       args.each { |a| @current_set.exclude_symbol(a, message) }
357:     end
exclude_vcall(*args) click to toggle source
     # File lib/rufus/treechecker.rb, line 363
363:     def exclude_vcall(*args)
364:       do_exclude_pair(:vcall, args)
365:     end
expand_class(arg) click to toggle source
     # File lib/rufus/treechecker.rb, line 331
331:     def expand_class(arg)
332: 
333:       if arg.is_a?(Class) or arg.is_a?(Module)
334:         [ parse(arg.to_s), parse("::#{arg.to_s}") ]
335:       else
336:         [ arg ]
337:       end
338:     end
extract_message(args) click to toggle source
     # File lib/rufus/treechecker.rb, line 323
323:     def extract_message(args)
324: 
325:       message = nil
326:       args = args.dup
327:       message = args.pop if args.last.is_a?(String)
328:       [ args, message ]
329:     end
parse(rubycode) click to toggle source

A simple parse (relies on ruby_parser currently)

     # File lib/rufus/treechecker.rb, line 515
515:     def parse(rubycode)
516: 
517:       self.class.parse(rubycode)
518:     end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.