There are a number of 'neat' things that can be done in dynamic languages that can be tucked away in parts of the code that aren't immediately obvious to another programmer or auditor as to the functionality of a given piece of code.
Consider this sequence in irb (interactive ruby shell):
NoMethodError: undefined method `foo' for "bar":String
from /usr/bin/irb:12:in `<main>'
irb(main):002:0> class String
irb(main):003:1> def foo
What happened there is I tried to call the method
foo in a String constant. This failed. I then opened up the String class and defined the method
foo o return
"foobar!", and then called it. This worked.
This is known as an open class and gives me nightmares every time I think of writing code in ruby that has any sort of security or integrity. Sure it lets you do some neat things quite fast... but I could make it so every time someone stored a string, it stored it to a file, or sent it over the network. And this little bit of redefining the String can be tucked anywhere in the code.
Many other dynamic languages have similar things that can be done. Perl has Tie::Scalar that can behind the scenes change how a given scalar works (this is a bit more obvious and requires a specific command that you can see, but a scalar that is passed in from somewhere else could be a problem). If you have access to the Perl Cookbook, look up Recipe 13.15 - Creating Magic Variables with tie.
Because of these things (and others often part of dynamic languages), many approaches to static analysis of security in code doesn't work. Perl and Undecidability shows this to be the case and points out even such trivial problems with syntax highlighting (
whatever / 25 ; # / ; die "this dies!"; poses challenges because the
whatever can be defined to take arguments or not at runtime completely defeating a syntax highlighter or static analyzer).
This can get even more interesting in Ruby with the ability to access the environment that a closure was defined in (see YouTube: Keeping Ruby Reasonable from RubyConf 2011 by Joshua Ballanco). I was made aware of this video from an Ars Technica comment by MouseTheLuckyDog.
Consider the following code:
t = block.binding.eval('(self.methods - Object.methods).sample')
f = Foo.new
This code is fully visible, but the
mal method could be somewhere else... and with open classes, of course, it could be redefined somewhere else.
Running this code:
~/$ ruby foo.rb
b.rb:20:in `qux': MWHWAHAW! (RuntimeError)
from b.rb:30:in `'
~/$ ruby foo.rb
b.rb:20:in `bar': MWHWAHAW! (RuntimeError)
from b.rb:29:in `'
In this code, the closure was able to access all of the methods and other bindings defined in the class at that scope. It picked a random method and redefined it to raise an exception. (see the Binding class in Ruby to get an idea of what this object has access to)
The variables, methods, value of self, and possibly an iterator block that can be accessed in this context are all retained.
A shorter version that shows the redefinition of a variable:
block.binding.eval('a = 43')
a = 42
Which, when run produces:
This is more than the open class that I mentioned above that makes static analysis impossible. What is demonstrated above is that a closure that is passed somewhere else, carries with it the full environment that it was defined in. This is known as a first class environment (just as when you can pass around functions, they are first class functions, this is the environment and all of the bindings available at that time). One could redefine any variable that was defined in the scope of the closure.
Good or bad, complaining about ruby or not (there are uses where one would want to be able to get at the environment of a method (see Safe in Perl)), the question of "why would ruby be restricted in for a government project" really is answered in that video linked above.
- Ruby allows one to extract the environment from any closure
- Ruby captures all bindings in the scope of the closure
- Ruby maintains all bindings as live and mutable
- Ruby has new bindings shadow old bindings (rather than cloning the environment or prohibiting rebinding)
With the implications of these four design choices, it is impossible to know what any bit of code does.
More about this can be read at Abstract Heresies blog. The particular post is about Scheme where such a debate was had. (related on SO: Why doesn't Scheme support first class environments?)
Over time, however, I came to realise that there was more difficulty and less power with first-class environments than I had originally thought. At this point I believe that first-class environments are useless at best, and dangerous at worst.
I hope this section shows the danger aspect of first class environments and why it would be asked to remove Ruby from the provided solution. Its not just that Ruby is a dynamic language (as mentioned else-answer, other dynamic languages have been allowed in other projects), but that there are specific issues that make some dynamic languages even more difficult to reason about.