/ruby_box

RubyBox allows the execution of untrusted Ruby code safely in a sandbox.

Primary LanguageRubyMIT LicenseMIT

RubyBox

Gem Version Build Status Code Climate Test Coverage Security

RubyBox allows the execution of untrusted Ruby code safely in a sandbox. It works by compiling Ruby code to JavaScript using opal and executing it in Google's V8 Engine with some help from mini_racer.

Installation

Add this line to your application's Gemfile:

gem 'ruby_box'

And then execute:

$ bundle

Or install it yourself as:

$ gem install ruby_box

Usage

# `RubyBox::Metal` is the sandbox base class. It has only the bare essentials to get the environment working.
class MySandbox < RubyBox::Metal
  # Code in the sandbox will block at most one second
  times_out_in 1.second

  # Makes the opal gem available for requiring inside the sandbox
  uses 'opal'

  # Requires the Opal compiler inside the sandbox (enables advanced runtime meta-programming like `Kernel#eval`)
  requires 'opal-parser'

  # Exposes the #native_add method to code running inside the sandbox
  exposes :native_add

  # Executes some code in the sandbox to setup it's runtime state
  executes <<-RUBY
    $global_state = 1337

    # Some boilerplate code
    class PlayThing
      attr_reader :name
    
      def initialize(name)
        @name = name
      end

      # Code inside of the sandbox can get a handle on the box with `RubyBox.current` and call exposed methods
      def add(a, b)
        RubyBox.current.native_add(a, b)
      end
    end
  RUBY

  def native_add(a, b)
    a + b
  end
end

untrusted_program = <<-RUBY
  $global_state = 'tainted'

  puts "Hello, world"
  
  car = PlayThing.new("Car")
  car.name
RUBY

# Every instance of the sandbox starts with the state configured on the class
my_sandbox = MySandbox.new
my_sandbox.execute(untrusted_program) #=> "Car"
my_sandbox.execute('PlayThing.add(2,7)') #=> 9
my_sandbox.stdout #=> ["Hello, world\n"]

# Every instance of the sandbox is isolated
another_sandbox = MySandbox.new
another_sandbox.execute('$global_state') #=> 1337

# It also has an stderr
another_sandbox.execute('warn "This looks dangerous"')
another_sandbox.stderr #=> ["This looks dangerous\n"]

# Exceptions comes through as subclasses of RubyBox::BoxedError
another_sandbox.execute('nil.no_method') #=> RubyBox::BoxedError::BoxedNoMethodError

# You can determine if you are in a sandbox using `RubyBox.boxed?` and `RubyBox.current`
RubyBox.boxed? #=> false
RubyBox.current #=> nil

Development

The development dependencies of this gem are managed using Bundler.

After checking out the repo, run bundle install to install dependencies. Then, run bundle exec rake spec to run the tests. You can also run bundle exec rake console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to RubyGems.

Contributing

Bug reports and pull requests are welcome on GitHub.

License

The gem is available as open source under the terms of the MIT License.