Project

cleanroom

0.2
Repository is archived
No commit activity in last 3 years
No release in over 3 years
Ruby is an excellent programming language for creating and managing custom DSLs, but how can you securely evaluate a DSL while explicitly controlling the methods exposed to the user? Our good friends instance_eval and instance_exec are great, but they expose all methods - public, protected, and private - to the user. Even worse, they expose the ability to accidentally or intentionally alter the behavior of the system! The cleanroom pattern is a safer, more convenient, Ruby-like approach for limiting the information exposed by a DSL while giving users the ability to write awesome code!
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 0
~> 3.0
 Project Readme

Ruby Cleanroom

Gem Version Build Status Code Climate

Ruby is an excellent programming language for creating and managing custom DSLs, but how can you securely evaluate a DSL while explicitly controlling the methods exposed to the user? Our good friends instance_eval and instance_exec are great, but they expose all methods - public, protected, and private - to the user. Even worse, they expose the ability to accidentally or intentionally alter the behavior of the system! The cleanroom pattern is a safer, more convenient, Ruby-like approach for limiting the information exposed by a DSL while giving users the ability to write awesome code!

The cleanroom pattern is a unique way for more safely evaluating Ruby DSLs without adding additional overhead.

Installation

Add this line to your application's Gemfile:

gem 'cleanroom'

And then execute:

$ bundle

Or install it yourself as:

$ gem install cleanroom

Usage

Setup

In order to use the cleanroom, you must first load the cleanroom gem:

require 'cleanroom'

Next, for any file you wish to be evaluated as a DSL, include the module:

class MyDSL
  include Cleanroom
end

Writing DSLs

For each public method you with to expose as a DSL method, call expose after the method definition in your class:

class MyDSL
  include Cleanroom

  def my_method
    # ...
  end
  expose :my_method

  def public_method
    # ...
  end

  private

  def private_method
    # ...
  end
end

In this example, MyDSL exposes two public API methods:

  • my_method
  • public_method

which would be accessible via:

instance = MyDSL.new
instance.my_method
instance.public_method

MyDSL also exposes one DSL method:

  • my_method

which would be accessible in a DSL file:

my_method

The use of the expose method has the added advantage of clearly identifying which methods are available as part of the DSL.

The method private_method is never accessible in the DSL or as part of the public API.

Evaluating DSLs

The cleanroom also includes the ability to more safely evaluate DSL files. Given an instance of a class, you can call evaluate or evaluate_file to read a DSL.

instance = MyDSL.new

# Using a Ruby block
instance.evaluate do
  my_method
end

# Using a String
instance.evaluate "my_method"

# Given a file at /file
instance.evaluate_file('/file')

These same methods are available on the class as well, but require you pass in the instance:

instance = MyDSL.new

# Using a Ruby block
MyDSL.evaluate(instance) do
  my_method
end

# Using a String
MyDSL.evaluate(instance) "my_method"

# Given a file at /file
MyDSL.evaluate_file(instance, '/file')

For both of these examples, the given instance is modified, meaning instance holds the values after the evaluation took place.

"Security"

The cleanroom gem tries to prevent unauthorized variable access and attempts to alter the behavior of the system.

First, the underlying instance object is never stored in an instance variable. Due to the nature of instance_eval, it would be trivial for a malicious user to directly access methods on the delegate class.

# Some DSL file
@instance #=> nil

Second, access to the underlying instance in the cleanroom is restricted to self by inspecting the caller attempts to access __instance__ from outside of a method in the cleanroom will result in an error.

# Some DSL file
__instance__ #=> Cleanroom::InaccessibleError
send(:__instance__) #=> Cleanroom::InaccessibleError

Third, the ability to create new methods on the cleanroom is also disabled:

# Some DSL file
self.class.class_eval { } #=> Cleanroom::InaccessibleError
self.class.instance_eval { } #=> Cleanroom::InaccessibleError

Fourth, when delegating to the underlying instance object, public_send (as opposed to send or __send__) is used. Even if an attacker could somehow bypass the previous safeguards, they would be unable to call non-public methods on the delegate object.

If you find a security hole in the cleanroom implementation, please email me at the contact info found in my GitHub profile. Do not open an issue!

Testing

If you are using cleanroom in your DSLs, you will likely want to test a particular DSL method is exposed. Cleanroom packages some RSpec matchers for your convienence:

# spec/spec_helper.rb
require 'rspec'
require 'cleanroom/rspec'

This will define the following matchers:

# Check against an instance
expect(:my_method).to be_an_exposed_method_on(instance)

# Check against a class
expect(:my_method).to be_an_exposed_method_on(klass)

# Check against an instance
expect(instance).to have_exposed_method(:my_method)

# Check against a class
expect(klass).to have_exposed_method(:my_method)

Contributing

  1. Fork the project
  2. Write tests
  3. Run tests
  4. Create a new Pull Request

License

Copyright 2014 Seth Vargo <sethvargo@gmail.com>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.