(More) safely evaluate Ruby DSLs with cleanroom
Switch branches/tags
Nothing to show
Latest commit 339f602 Oct 24, 2016 @sethvargo committed on GitHub Merge pull request #4 from ismail/master
Make the license string SPDX compatible, see https://spdx.org/licenses/


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.


Add this line to your application's Gemfile:

gem 'cleanroom'

And then execute:

$ bundle

Or install it yourself as:

$ gem install cleanroom



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

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
    # ...
  expose :my_method

  def public_method
    # ...


  def private_method
    # ...

In this example, MyDSL exposes two public API methods:

  • my_method
  • public_method

which would be accessible via:

instance = MyDSL.new

MyDSL also exposes one DSL method:

  • my_method

which would be accessible in a DSL file:


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

# Using a String
instance.evaluate "my_method"

# Given a file at /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

# 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.


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!


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)


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


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


Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
See the License for the specific language governing permissions and
limitations under the License.