Access Control in Ruby
samwho keyboard logo

Access Control in Ruby

Access control in Ruby feels, to me, a little different to access control in other languages. In this post I'm going to talk about it, demonstrate ways around it and ultimately give my opinion on why I think access control in general is a little bit pointless.

Note: This is going to be fairly light-hearted and not-practical. If you truly need something to be private in your code there are best practices involving databases and encryption. This post just aims to demonstrate the possibilities in Ruby and have some fun with them.

Prerequisite Knowledge: Knowing what access control is will help, but I'll provide an explanation at the beginning of the post anyway. Basic Ruby is required. Some of the metaprogramming stuff might be confusing but bear with it. If you don't know what instance_eval does, I recommend looking it up before reading this post.

# What is access control?

Access control is the jargon used to describe whether a member variable in an object is public, private, protected or any other variation defined by the language in question. A brief overview of the generally accepted definitions of the popular three:

However, Ruby and its dynamic nature treats access control a little differently. I don't want to restate anyone, and there's a fantastic blog post that has already covered this beautifully so I'll just link to it: Alan Skorkin on Access Control in Ruby. Please read it before continuing.

# Circumventing Access Control

One of the arguments I hear, and was taught at University, is that access control stops people screwing with and invalidating the internal state of your object. So by declaring all of your attributes as private and defining getters and setters to them, you are controlling the data that goes into your object.

# Instance variables

Take the following example:

class Foo
  def initialize
    @secret = 42
  end
end

It's naive to believe that this is private in any way. It shows up when you inspect the object:

f = Foo.new
#=> #<Foo:0x000000018ea7f0 @secret=42>

You can also straight up access it through instance_variable_get:

f = Foo.new
f.instance_variable_get :@secret
#=> 42

So instance variables are most certainly not private and, as far as I can tell, there's no way to make them so.

# Private methods

So let's up our game. Let's try something a bit better and define ourselves an explicitly private method:

class Foo
  private
  def secret
    42
  end
end

This time we won't see anything revealing when we initialize an object of Foo:

f = Foo.new
#=> #<Foo:0x0000000152c3e8>

And we definitely cannot call the method:

f = Foo.new
f.secret
#=> NoMethodError: private method `secret' called for #<Foo:0x0000000152c3e8>
#      from (irb):8
#      from /home/sam/.rvm/rubies/ruby-1.9.2-p320/bin/irb:16:in `<main>'

So on the surface it seems pretty safe. Unfortunately, using some popular Ruby metaprogramming techniques it's very easy to access this private variable from anywhere in a program. Two examples:

# Accessing private methods with send

f = Foo.new
f.send :secret
#=> 42

The send method is available to every object in Ruby and simply acts as if it's calling a method with the same name as the symbol passed in. It doesn't stop for anybody, not even the private declaration.

# Accessing private methods with instance_eval

f = Foo.new
f.instance_eval { secret }
#=> 42

instance_eval allows you to execute code as if it was written inside the instance of the object it was called on. It, too, is available on all objects and allows us to freely access the secret method without so much as a whimper.

# Defending against send and instance_eval

Okay. So how about we try and stop people from using these techniques to get at our private data? Look at this class:

class Foo
  def send *args
    nil
  end

  def instance_eval *args, &block
    nil
  end

  private

  def secret
    42
  end
end

Here we're overriding the two methods we saw before that allowed people to get at our private method in the hope that will totally block access.

Still, this won't protect our data:

Foo.class_eval { remove_method :send }
f = Foo.new
f.send :secret

The remove_method method is available in all Ruby classes and will just discard of a method definition so that people can once again get at our data.

What about if we override class_eval as well? class_eval is what was letting people get into the Foo class and remove the send method override. Here's the new class with even better security:

class Foo
  def send *args
    nil
  end

  def instance_eval *args, &block
    nil
  end

  def self.class_eval *args, &block
    nil
  end

  private

  def secret
    42
  end
end

Again, this just won't work. There's nothing stopping people reopening the class and just removing the method that way:

class Foo
  remove_method :send
end

f = Foo.new
f.send :secret
#=> 42

And I honestly can't see a way around that one, which ends this particular branch of madness.

# Another technique worth mentioning...

If you do somehow figure out how to get around all of the above things that have been mentioned, let's not forget the following possibility:

class Foo
  puts method(self.methods.first).source_location
end

It won't tell you exactly where the secret value is and it won't work if there are no methods defined in the class, but if the conditions are right then you'll get output that looks like this:

["foo.rb", 10]

Which is the relative file name and the line of the first method in that file. Ruby is an interpreted language. By necessity, the user will have your source code. It's pointless trying to make anything private any way.

# So what's the point of access control?

The idea of access control in Ruby is to serve as a guide. If a developer marks something as private or protected, they are trying to make it clear to you that you really need to know what you're doing if you want to play around with it. Apart from that, there is very little point of marking things as private or protected.

Even in strongly typed languages such as Java that enforce access control pretty strongly (though it is still circumventable) I think it's pointless, if not harmful. Occasionally the developer is held back by the fact something is marked as private (a useful helper method for example) a situation I have found myself in more than once using third party libraries in the past. Sure, it makes the auto-complete list look cleaner when there are no private methods in it but sometimes it's incredibly helpful to have access to them. I know the implications, I'm not stupid.

You'll rarely find anything marked as private in my code. If I ever wanted to mark something as private, I would do it in a conventional way, using leading underscores, rather than trying to hide things on the top shelf so to speak.

# Conclusion

I hoped you enjoyed this trip down the rabbit hole. If you can think of any other creative ways of getting access to code people don't want you to access please let me know. Stretching languages is always fun and it's a practice well worth engaging in :)

# Disclaimer

Before getting angry and telling me it's stupid to try and hide sensitive information in private variables, I assure you that I am well aware. The idea of this post is to demonstrate that it's very easy to get around the private declaration and hopefully demonstrate some cool metaprogramming techniques to people who may not be familiar. It's not intended to be a tutorial on how make "truly private" methods in Ruby, I doubt such a thing is even possible.

powered by buttondown