Isn’t this like super dangerous?
Thanks you for your feedback !
Bundler use the same approach to evaluate gemspecs file. And almost the same approach for the Gemfile..
So, as bundler is used thousands of times per day, then the practice doesn’t seems so dangerous.. hehe
The main point here is that I use eval in a safe context: reading a file that will be edited by the developers themselves.
It’s not an eval in a controller action.
Don’t developers use gemfiles (and in this case, config files) written by unknown third parties (other developers) too, though? I don’t want to have to audit a config file for security.
Downloading code from the internet and then running it is a security vulnerability. It really is a wonder that most language specific package managers don’t trouble themselves with anything as sophisticated as an integrity check, let alone a signature, let alone any kind of sandboxing by default.
Ruby isn’t the only offender, in most languages, “bundler doesn’t work try running it with sudo, there you go” is standard operating procedure.
There are real package managers out there that attempt to tackle these problems and more (like guix), but they’re not the norm, and I don’t think any of them are perfect.
We considered this when building RubyGems, and in fact there was a competing ruby package manager at the time called RPA that tried to address these issues (https://github.com/threedaymonk/rpa-base). The problem is that for signatures to be meaningful, you have to establish a web of trust.
Since that’s a hard problem, and we were building a package manager from scratch at the back of a hotel bar at a programming conference so it could be demoed the next day, we decided to stick with what we could solve in an evening (and in any case installing a gem from rubygems.org is at least as secure as installing a tarball from the Ruby Application Archive, which is what we were hoping to replace (http://web.archive.org/web/*/http://raa.ruby-lang.org/)).
I still hope that one day someone will figure out a way for programmers to share code with each other with minimal friction, but also be able to trust the code that is being shared without accepting it on blind faith.
Having the gemspec be arbitrary Ruby code is a problem. Less so on the client and enduser (it runs most of the code downloaded from rubygems.org anyways, it doesn’t add much) than on the systems that actually need to digest them, like the rubygems host itself! AFAIK, for that reason, rubygems dumps the data it needs locally on the machine before publish. AFAIK also, the gemspec is not loaded on installation.
But that still means all tooling that wants to look at the gemspec (dependency update bots etc.) need to run it to get all the data out, especially because people do modify the gemspec to read external resources or similar.
There’s tons of problems coming out of that, which is why most second-generation bundlers don’t use code (interestingly, https://conan.io/ though uses Python for that).
The Gemfile being code is less so a problem, as it’s the code component that loads the dependencies in your app. It’s usually handwritten and frequently looked at. If I wanted to hide something, I’d hide it in a loaded dependency, as Ruby can evaluate arbitrary code during require even.
Dangerous in which sense?
I will agree that eval should generally be avoided, since it can accidentally clobber local variables. The code in the post does not uses eval, however; it uses instance_eval.
If you mean dangerous in a security sense, then you are correct that you should not pass untrusted strings to eval/instance_eval/class_eval. If the string is from an untrusted source, it should be evaluated inside a sandbox. There are a number of libraries/gems that provide sandbox functionality, though I’m not sure which is the best to use these days.
The code was eval when I wrote that an was later changed to instance_eval.
Please don’t use GitHub for code blocks. It is almost unreadable on iOS, and services like Instapaper and Safari’s readability view do not support them. Medium supports normal <pre> code blocks.
Perl6 and lisp both have native capability to parse DSLs.