0.0
No commit activity in last 3 years
No release in over 3 years
Run erb templates safely within a sandbox.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.2.3
~> 1.6.1

Runtime

>= 0.2.2
 Project Readme

sandboxed_erb¶ ↑

This is a gem that allows you to run an ERB template in a sandbox so it is safe to expose these templates to your customers to allow them to customise your application (or any other use you can think of).

This has been inspired by github.com/tario/shikashi, a ruby sandbox which uses the evalhook gem to intercept and rewrite every ruby call to go through an access check at runtime. It was originally designed to run in the shikashi sandbox, but was found to be too slow as every call was being intercepted and analysed at runtime. Because a templating language does not need everything ruby offers, i have limited the allowed synax to a safer subset at compile time to reduce the number of runtime checks required.

To install¶ ↑

sudo gem install sandboxed_erb

How It Works¶ ↑

The code does not run in a ‘sandbox’ like javascript, it is actually processed into ‘safe’ code then run using the normal ruby intepreter.

  1. The template is first processed by the ERB compiler to produce valid ruby code.

  2. The generated erb code is then processed to check that if conforms to the ‘whitelist’of allowed syntax.

  3. Every invokation on an object is converted from some_object.some_method(arg1,argn) to some_object._sbm(:some_method,arg1,argn).

  4. Run using ruby intepreter.

  5. The _sbm method checks at runtime that the method is allowed as per rules defined by ‘Module.sandboxed_methods’ and ‘Module.not_sandboxed_methods’

Why Is The Code Safe?¶ ↑

  • I use a ‘white list’ of allowed syntax, so if I’ve missed something it will be denied.

  • I dont allow the defining of any classes, methods or modules.

  • You cannot access global variables or constants (or define them)

  • Every call is routed through the _sbm method, so if an non-safe object is somehow called, it wont have the _sbm method, so it will error.

_sbm (SandBoxed Method)¶ ↑

Heres an example of the generated code after is has been processed:

Source:

email_address = user.login + "@" + user.domain(:local_domain)

Processed Code

email_address = user._sbm(:login)._sbm(:+,"@")._sbm(:+,user._sbm(:domain, :local_domain))

_smb will check that the symbol of the function to call (:login and :domain) has been explicitly allowed for that object using the sandboxed_methods module function (example below). If it has been allowed, it is assumed that the method getting called is safe (developers responsibility!) and that it can handle the arguments (which should be safe because you cannot define methods etc in a sandbox).

Optimisations¶ ↑

  • The ERB template generates many _erbout.concat calls, these are not routed through _sbm.

  • to_s is called heaps, it is assumed .to_s is safe to all (without arguments) an any object.

Benchmark Results¶ ↑

Taken from profile/vs_liquid.rb

                         user     system      total        real
erb template         0.030000   0.000000   0.030000 (  0.024003)
sandboxed template   0.100000   0.000000   0.100000 (  0.100563)
liquid template      3.040000   0.020000   3.060000 (  3.116854)

Examples¶ ↑

Calling some sandboxed methods¶ ↑

You can define a class and specify what methods are safe to use from within the sandbox using the sandboxed_methods function.

#Define a class we want accessable from the sandbox
class SandboxedClass

  sandboxed_methods :method_i_can_call

  def method_i_can_call(arg1)
    "{arg1} passed in"
  end

  def private_method(arg1)
    "You cannot call this from the sandbox"
  end
end

#a basic template that calls 'method_i_can_call' on an instance of SandboxedClass passed in below
str_template = "test = <%=sandboxed_class.method_i_can_call('some value')%>"

template = SandboxedErb::Template.new
#compile the template so it can get run multiple times
template.compile(str_template)
#run the template, passing in an instance of SandboxedClass as a variable called 'sandboxed_class'
result = template.run(nil, {:sandboxed_class=>SandboxedClass.new})

#result = "test = some value passed in"

Mixins / Helper functions¶ ↑

To add helper functions to the template, you can define the helper function mixins when the template is instantiated.

#define helper functions in a module
module MixinTest
  def test_mixin_method 
    "TEST #{@controller.some_value}"  #@controller is a 'context' object
  end
end

#define a 'context' object that the helper function will have access to
class FauxController
  def some_value
    "ABC"  
  end
end

faux_controller = FauxController.new

str_template = "mixin = <%= test_mixin_method %>"
#declare the template, passing in the MixinTest module so its test_mixin_method will be available as a helper function
template = SandboxedErb::Template.new([MixinTest])
template.compile(str_template)
#pass the FauxController object as a context object called @controller (the keys are converted to member variables)
result = template.run({:controller=>faux_controller}, {}) 

#result =  "mixin = TEST ABC"

Contributing to sandboxed_erb¶ ↑

  • Check out the latest master to make sure the feature hasn’t been implemented or the bug hasn’t been fixed yet

  • Check out the issue tracker to make sure someone already hasn’t requested it and/or contributed it

  • Fork the project

  • Start a feature/bugfix branch

  • Commit and push until you are happy with your contribution

  • Make sure to add tests for it. This is important so I don’t break it in a future version unintentionally.

  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.

Copyright © 2011 Mark Pentand. See LICENSE.txt for further details.