I posted the other day about Groovy’s SecureASTCustomizer and how it is harmful. In the end of that post, I suggested that doing the check dynamically would work better.
So in this installaition of “Project Of The Day”, I went ahead and implemented it. The result is Groovy sandbox.
My main idea is to confine the sandboxed script into its local object graph. The script should be allowed to mutate this graph all it wants, but it’ll be only allowed to include objects of known whitelisted “safe” types (such as String, List, Date etc.), and a few known safe instances, which acts as a bridge between the sandbox and the rest of the world. These bridge objects would have to be written carefully.
To use this, you have to add SandboxTransformer to your CompilerConfiguration first:
def cc = new CompilerConfiguration()
cc.addCompilationCustomizers(new SecureTransformer())
def binding = new Binding();
binding.robot = robot = new Robot();
def sh = new GroovyShell(binding,cc)
And now any script compiled via the resulting shell object will be sandboxed.
When a sandboxed script executes, all of the following operations are intercepted.
- static/instance method invocation foo.bar(….)
- object allocation new Foo(…)
- property access and assignment zot=foo.bar / foo.bar=zot
- attributes access and assignment zot=foo.@bar / foo.@bar=zot
- array access and assignment zot=foo[bar] / foo[bar]=zot
To examine those calls and reject some of them, create your own implementation of GroovyInterceptor and registers it to the thread before you start executing the script:
def sandbox = new RobotSandbox()
sandbox.register()
try {
sh.evaluate("robot.leftArm.move()") // this is allowed to complete
sh.evaluate("robot.selfDestruct()") // no!
} finally {
sandbox.unregister()
}
See the robot example for a complete example.
Now let’s see if I can get some feedback from real Groovy experts, and see if they’d be willing to take this into Groovy itself…