15
votes

I am currently working on a text-based game engine in Ruby, with the app separated into Ruby code in /lib and YAML data in /data, which is loaded when needed by the game. I want to allow the data files to contain basic scripts, mostly in an event/observer model. However, I also want users to be able to generate and share custom scenarios without having to worry about malicious code embedded in the script.

Addendum: My original plan was to have user-created content separated into two types, "modules" which were data-only (and thus safe) and plugins which added additional functionality (but obviously were not safe). To make an analogy to tabletop gaming, modules would be like published adventure scenarios and content, and plugins would be rulebooks containing additional rules and systems.

Sample script (syntax of course subject to change based on solution):

---
Location:
  observers:
    on_door_open: |
      monster = spawn_monster(:goblin);
      monster.add_item(random_item());
      monster.hostile = true;

From a security standpoint, it would be ideal if scripting was strictly opt-in, probably through an included mixin with a little DSL, e.g.:

class Frog
  include Scriptable

  def jump; ... ; end # this can be called from a script
  allow_scripting :jump

  def ribbit; ... ; end # this cannot be called from a script
end

I've looked at three four options, but I'm not sure which is the best approach to take:

  1. Use Ruby scripting, but in a sandbox of some kind.

    Pros: Very familiar with Ruby, no need for "glue" code or issues integrating objects between languages.

    Cons: Not very familiar with security issues or sandboxing, haven't found any out-of-the-box solutions that seem to fit.

  2. Implement Embed another scripting language, e.g. Lua.

    Pros: Ruby and Lua are C-based, so bindings should be reasonably simple. Lua is a reasonably popular language, so help available if I run into issues later. Secure, since any functionality I don't specifically bind will be unavailable from scripts.

    Cons: Existing Ruby-Lua bindings seem to be one-way, old and poorly maintained, or both. Seems a mite dodgy to embed a scripting language inside another scripting language.

  3. Implement a custom scripting language with Ruby interpreter. I've been experimenting with Treetop, and it shouldn't be too hard to make a simple grammar that would suffice for the scripts.

    Pros: No need to embed another language. Only functionality I've specifically implemented will be available to scripts.

    Cons: Overkill. "Not built here" syndrome. Probably horrible nest of bugs waiting to happen.

  4. Implement the data files entirely in Ruby, using a domain-specific language.

    Pros: Simple and easy.

    Cons: No user-created data is trustable.

I am also open to other suggestions not on that list that I may not have thought of. What is the best solution to safely implement scripts embedded in data files?

Edit 2011年12月23日: Added fourth option with DSL, added "addendum" at top with additional thoughts/context.

2
Hmm, I'm not so familiar with game scripting, but why not use V8 and Javascript? It's lightning fast and most everyone will be comfortable working with JS. - omninonsense
It really depends on what you want to achieve, if the plugins are in ruby it will be hard to prevent one of them to reopen a core class and do anything they want in the same way a ruby on rails plugin can do anything it wants. Aside of that using any other language as a plugin language is overkill, you will do more work on it than the game itself ;) - Schmurfy
If I decide to embed a language, I would probably lean toward Lua instead of Javascript (because I need an excuse to learn the former). I've edited the original post to add a bit of context on my original ideas on plugins. - Rob Smith
Thanks to everyone who answered or commented, I decided to go with a lean domain-specific language in Ruby for simplicity's sake - this is a personal project and it's rather unlikely that I'd have other users to begin with, let alone user-generated content, so I'll burn the security bridge when I come to it. - Rob Smith
What about your DSL, did it work well? Another option that was not discussed here is to use V8 through the gem called "therubyracer". Secure by default, and even faster than a Ruby "eval" in the benchmark I just did. - plang

2 Answers

5
votes

You might consider using the Shikashi gem, which allows you to create sandboxes and define a whitelist of allowed method calls on individual objects.

1
votes

Consider using jRuby instead of Ruby. Java was originally implemented to support mobile code (back in the set-top days) and has a well-tested security model/implementation that could, I suspect, wrap enough of jRuby to keep user scripts/classes from creating havoc with the rest of the game system. jRuby supports embedding, as well, which may help separate the game core from the user applications, although I do not know how robust it is at this time.

And, of course, jRuby is Ruby!