<|start_filename|>test/exploits-test.js<|end_filename|>
var assert = require('assert'),
  SandCastle = require('../lib').SandCastle,
  fs = require('fs');

describe('__defineGetter__-exploit', function () {
  it('should not execute untrusted code as a result of __defineGetter_ exploit', function (finished) {
    // fix for exploit: https://github.com/bcoe/sandcastle/pull/21
    var sandcastle = new SandCastle(),
      script = sandcastle.createScript(fs.readFileSync("./test/fixtures/exploit-getter.txt").toString());

    script.on('exit', function(err, output) {
      setTimeout(function() {
        sandcastle.kill();
        assert.equal(fs.existsSync('./owned.txt'), false);
        finished();
      }, 2000);
    });

    script.run({parameters: {string: 'hello!'}});
  });
});

describe('arguments.callee-exploit', function() {
  it('should not execute untrusted code as a result of walking the arguments.callee chain', function(finished) {
    // fix for exploit: https://github.com/bcoe/sandcastle/pull/21
    var sandcastle = new SandCastle(),
      script = sandcastle.createScript(fs.readFileSync("./test/fixtures/exploit-callee.txt").toString());

    setTimeout(function() {
      sandcastle.kill();
      assert.equal(fs.existsSync('./owned.txt'), false);
      finished();
    }, 2000);

    script.run({parameters: {string: 'hello!'}});
  });
});


<|start_filename|>bin/sandcastle.js<|end_filename|>
#!/usr/bin/env node

var sandcastle = require('../lib'),
  argv = require('optimist').argv,
  mode = argv._.shift();

switch (mode) {
  case 'sandbox':
    (new sandcastle.Sandbox({
        socket: (argv.socket || '/tmp/sandcastle.sock')
    })).start();
    break;
  default:
    console.log('Usage sandcastle <command>\n\n\
      \t<sandbox>\tstart a sandbox server\n\
      \t\t--socket=[path to socket file]\n\
    ')
}

<|start_filename|>lib/script.js<|end_filename|>
var _ = require('lodash'),
  net = require('net'),
  events = require('events'),
  util = require('util'),
  BufferStream = require('bufferstream'),
  Buffer = require('buffer').Buffer;

function Script(opts) {
  events.EventEmitter.call(this);
  
  _.extend(this, {
    source: '',
    socket: '/tmp/sandcastle.sock',
    timeout: 5000,
    exited: false,
    sandcastle: null // the parent sandcastle executing this script.
  }, opts);
};

util.inherits(Script, events.EventEmitter);

Script.prototype.run = function(methodName, globals) {
  var _this = this;

  // passed in methodName argument is the globals object
  if (globals === undefined && typeof methodName === 'object') {
    globals = methodName;
    methodName = null;
  }

  // we default to executing 'main'
  methodName = methodName ? methodName : 'main';

  this.reset();

  this.createTimeout(methodName);

  this.createClient(methodName, globals);
};

Script.prototype.createTimeout = function(methodName) {
  var _this = this;

  if (this.timeoutId) clearTimeout(this.timeoutId);

  this.timeoutId = setTimeout(function() {
    if (_this.exited) return;
    _this.exited = true;
    _this.sandcastle.kickOverSandCastle();
    _this.emit('timeout', methodName);
  }, this.timeout);
};

Script.prototype.reset = function() {
  if (this.timeoutId) clearTimeout(this.timeoutId);
  this.exited = false;
};

Script.prototype.createClient = function(methodName, globals) {

  var _this = this;

  this.sandcastle.sandboxReady(function() {

    if (_this.exited) return;

    var client = net.createConnection(_this.sandcastle.getSocket(), function() {
      client.write(JSON.stringify({
        source: _this.source,// the untrusted JS.
        sourceAPI: _this.sourceAPI,// the trusted API.
        globals: JSON.stringify(globals), // trusted global variables.
        methodName: methodName
      }) + '\u0000\u0000'); // the chunk separator
    });

    client.on('close', function() {
      if (!_this.dataReceived) {
        setTimeout(function() {
          _this.createClient(methodName, globals);
        }, 500);
      }
    });

    client.on('error', function(err) {
      setTimeout(function() {
        _this.createClient(methodName, globals);
      }, 500);
    });


    var stream = new BufferStream({size:'flexible'});
    stream.split('\u0000\u0000', function(chunk) {
      client.end();
      _this.onExit(methodName, chunk);
    });

    stream.split('\u0000', function (chunk) {
      _this.onTask(client, methodName, chunk); // handling the task
    });

    client.on('data', function(chunk) {
      _this.dataReceived = true;
      stream.write(chunk);
    });

  });
};

Script.prototype._parseChunk = function(data) {
  var output = null,
    error = null;

  try {
    if (data.toString() !== 'undefined') {
        output = JSON.parse(data);
        if (output !== null && output.error) {
            error = new Error(output.error.message);
            error.stack = output.error.stack;
            output = null;
        }
    }
  } catch (e) {
    error = e;
  }

  return {
    output: output,
    error: error
  };
};

Script.prototype.onExit = function(methodName, data) {
  var _this = this,
    parsed = {
      output: null,
      error: null
    };

  if (this.exited) return;

  this.exited = true;

  parsed = _this._parseChunk(data);

  this.emit('exit', parsed.error, parsed.output, methodName);
};

Script.prototype.onTask = function(client, methodName, data) {
  var _this = this,
    parsed = _this._parseChunk(data);

  if (this.exited) return;

  parsed.output = parsed.output || {};
  parsed.output.options = parsed.output.options || {};

  this.emit('task', parsed.error, parsed.output.task, parsed.output.options, methodName, function (answerData) {
    if (_this.refreshTimeoutOnTask) _this.createTimeout(methodName);
    client.write(JSON.stringify({ task: parsed.output.task, data: answerData }) + '\u0000');
  });
};

Script.prototype.setSandCastle = function(sandcastle) {
  this.sandcastle = sandcastle;
};

exports.Script = Script;


<|start_filename|>lib/index.js<|end_filename|>
exports.Pool = require('./pool').Pool;
exports.SandCastle = require('./sandcastle').SandCastle;
exports.Sandbox = require('./sandbox').Sandbox;

<|start_filename|>examples/api-example.js<|end_filename|>
// example of using an external API. In this case
// we pul lin lodash's extend.
var SandCastle = require('../lib').SandCastle;

var sandcastle = new SandCastle({
  api: './examples/api.js', // We provide an API with the setTimeout function.,
});

var script = sandcastle.createScript("\
  exports.main = function() {\
    var o1 = {a: 2};\
    var o2 = {b: 3};\
    extend(o1, o2);\
    exit(JSON.stringify(o1));\
  }\
");

script.on('exit', function(err, output) {
    console.log(output); // Hello World!
    process.exit(0);
});

script.run();


<|start_filename|>test/sandcastle-test.js<|end_filename|>
var equal = require('assert').equal,
  assert = require('assert'),
  notEqual = require('assert').notEqual,
  SandCastle = require('../lib').SandCastle,
  Pool = require('../lib').Pool,
  path = require('path');

describe('SandCastle', function () {
  it('should fire on("exit") when a sandboxed script calls the exit method', function (finished) {
    var sandcastle = new SandCastle();

    var script = sandcastle.createScript("\
      exports.main = function() {\
        var result = 1 + 1;\
        exit({\
          results: [result]\
        });\
      }\
    ");

    script.on('exit', function (err, result, methodName) {
      sandcastle.kill();
      equal(result.results[0], 2);
      equal('main', methodName);
      finished();
    });

    script.run();
  });

  it('should pass global variables to run()', function (finished) {
    var sandcastle = new SandCastle();

    var script = sandcastle.createScript("\
      exports.main = function() {\
        exit(foo);\
      }\
    ");

    script.on('exit', function (err, result, methodName) {
      sandcastle.kill();
      equal('bar', result);
      equal('main', methodName);
      finished();
    });

    script.run({foo: 'bar'});
  });

  it('should run the specified function and pass global variables correctly', function (finished) {
    var sandcastle = new SandCastle();

    var script = sandcastle.createScript("\
      exports = {\
        foo: function () {\
          exit(foo);\
        }\
      };\
    ");

    script.on('exit', function (err, result, methodName) {
      sandcastle.kill();
      equal('bar', result);
      equal('foo', methodName);
      finished();
    });
    script.run('foo', {foo: 'bar'});
  });

  it('should not allow require() to be called', function (finished) {
    var sandcastle = new SandCastle();

    var script = sandcastle.createScript("\
      exports.main = function() {\
        var net = require('net');\
      }\
    ");

    script.on('exit', function (err, result) {
      sandcastle.kill();
      assert(err.message.match(/require is not defined/));
      finished();
    });

    script.run();
  });

  it('should able to exit(null)', function (finished) {
    var sandcastle = new SandCastle();

    var script = sandcastle.createScript("\
      exports.main = function() {\
        exit(null);\
      }\
    ");

    script.on('exit', function (err, result) {
      sandcastle.kill();
	  equal(result,null);
      finished();
    });

    script.run();
  });

  it('should provide an API', function (finished) {
    var sandcastle = new SandCastle({
      api: './examples/api.js'
    });

    var script = sandcastle.createScript("\
      exports.main = function() {\
        getFact(function(fact) {\
          exit(fact);\
        });\
      }\
    ");

    script.on('exit', function (err, result) {
      sandcastle.kill();
      equal(result, 'The rain in spain falls mostly on the plain.');
      finished();
    });

    script.run();
  });

  it('should allow API to be passed in as string', function(finished) {
    var sandcastle = new SandCastle({
      api: "_ = require('lodash');\
        exports.api = {\
          map: function(values, cb) {\
            return _.map(values, cb);\
          }\
      }"
    });

    var script = sandcastle.createScript("\
      exports.main = function() {\
        exit(map([{a: 1}, {a: 2}, {a: 3}], function(obj) {\
          return obj.a;\
        }))\
      }\
    ");

    script.on('exit', function (err, result) {
      sandcastle.kill();
      equal(JSON.stringify(result), JSON.stringify([1, 2, 3]));
      finished();
    });

    script.run();
  });

  it('should provide an API and a per-script API', function (finished) {
    var sandcastle = new SandCastle({
      api: './examples/api.js'
    });

    var script = sandcastle.createScript("\
      exports.main = function() {\
        callAdditional(function(fact) {\
          exit(fact);\
        });\
      }\
    ", {extraAPI: "function anotherFunction(cb) { cb('The reign in spane falls mostly on the plain') }" });

    script.on('exit', function (err, result) {
      sandcastle.kill();
      equal(result, 'The reign in spane falls mostly on the plain');
      finished();
    });

    script.run();
  });

  it('should timeout and respawn when looping script runs', function (finished) {
    this.timeout(10000);

    var sandcastle = new SandCastle({
      api: './examples/api.js',
      timeout: 2000
    });

    var loopingScript = sandcastle.createScript("\
      exports.main = function() {\
        while(true) {};\
      }\
    ");

    var safeScript = sandcastle.createScript("\
      exports.main = function() {\
        exit('banana');\
      }\
    ");

    safeScript.on('exit', function (err, result) {
      sandcastle.kill();
      equal(result, 'banana');
      finished();
    });

    loopingScript.on('timeout', function (methodName) {
      equal(methodName, 'main');
      safeScript.run();
    });

    loopingScript.run();
  });

  it('should return a human readable stacktrace', function (finished) {
    var sandcastle = new SandCastle();

    var script = sandcastle.createScript("\
        exports.main = function() {\n\
          var net = require('net');\n\
        }\n\
      "
    );

    script.on('exit', function (err, result) {
      sandcastle.kill();
      equal(err.stack.indexOf('2:21') > -1, true);
      finished();
    });
    script.run();
  });

  it('should enforce memory limit', function (finished) {
    this.timeout(10000);

    var sandcastle = new SandCastle({memoryLimitMB: 10});

    var script = sandcastle.createScript("\
        exports.main = function() {\n\
          var test = []; \
          for(var i = 0; i < 5000000; ++i) { \
            test[i] = 'a'; \
          } \
          exit(1); \
        }\n\
      "
    );

    script.on('exit', function (err, result) {
      // Should not go here.
      equal(false, true);
    });

    script.on('timeout', function (err, result) {
      sandcastle.kill();
      finished();
    });
    script.run();
  });

  it('sets cwd, when running scripts', function(finished) {
    var cwd = process.cwd() + '/test';

    var sandcastle = new SandCastle({
      api: '../examples/api.js',
      cwd: cwd
    });

    var script = sandcastle.createScript("\
      exports.main = function() {\
        exit({ cwd: cwd() });\
      }\
    ");

    script.on('exit', function(err, result) {
      equal(cwd, result.cwd);
      sandcastle.kill();
      finished();
    });

    script.run();
  });

  it('should allow api to see globals',function(finished){
    var sandcastle = new SandCastle({
      api: './examples/contextObjectApi.js'
    });

    var script = sandcastle.createScript("\
     exports.main = function() {\n\
       var globalState = {};\n\
       if(typeof state === \"undefined\"){\n\
         globalState = 'none';\n\
       }\n\
       else{\n\
         globalState = state;\n\
       }\n\
       exit({\n\
         globalState:globalState,\n\
         apiState:stateManager.getState()\n\
       });\n\
     }\n");

    script.on('exit', function(err, result) {
       equal(result.globalState, 'none');
       equal(result.apiState.key, 'val');
       sandcastle.kill();
       finished();
    });

    script.run({state:{key:'val'}});
  });

  it('should correctly run the "test" task and and return the result', function (finished) {
    var sandcastle = new SandCastle();

    var script = sandcastle.createScript("\
      exports = {\
        onTestTask: function (data) {\
          if (data.count > 1) {\
            exit(data);\
          }\
          else {\
            runTask('test', data);\
          }\
        },\
        main: function() {\
          runTask('test', {count: 0});\
        }\
      }\
    ");

    script.on('task', function (err, taskName, options, methodName, callback) {
        options.count++;

        equal(taskName, 'test');
        equal(methodName, 'main');

        return callback(options);
    });

    script.on('exit', function (err, result) {
      equal(result.count, 2);
      sandcastle.kill();
      finished();
    });

    script.run();
  });
});


<|start_filename|>test/pool-test.js<|end_filename|>
var equal = require('assert').equal,
  notEqual = require('assert').notEqual,
  Pool = require('../lib').Pool;

describe('Pool', function () {
  it('should run multiple scripts', function (finished) {
    var pool = new Pool({numberOfInstances: 5});

    var scriptsExited = 0;

    for (var i = 0; i < 20; ++i) {
      var script = pool.createScript("\
          exports.main = function() {\n\
            exit(testGlobal);\n\
          }\n\
        "
      );
      script.on('exit', function (err, result) {
        scriptsExited++;
        if (scriptsExited == 10) {
          pool.kill();
          equal(10, result);
          finished();
        }
      });
      script.run({testGlobal: 10});
    }
  });

  it('should run scripts on non blocking instances', function (finished) {
    var pool = new Pool({numberOfInstances: 2});
    var exited = false;
    // Create blocking script.
    var script = pool.createScript("\
        exports.main = function() {\n\
          while(true);\
        }\n\
      "
    );
    script.run();

    var scriptsExited = 0;
    for (var i = 0; i < 10; ++i) {
      var script2 = pool.createScript("\
          exports.main = function() {\n\
            exit(10);\n\
          }\n\
        "
      );
      script2.on('exit', function (err, result) {
        scriptsExited++;
        if (scriptsExited == 10) {
          pool.kill();
          equal(10, result);
          exited = true;
          finished();
        }
      });
      script2.run();
    }
    setTimeout(function () {
      if (!exited) {
        equal(false, true);
      }
    }, 3000);
  });

  it('should not be created if there are zero or negative amount of specified instances', function (finished) {
    var pool = null;
    try {
      pool = new Pool({numberOfInstances: 0});
      equal(false, true);
    } catch (error) {
      notEqual(-1, error.indexOf("Can't create a pool with zero instances"));
      finished();
    }
  });
});


<|start_filename|>examples/contextObjectApi.js<|end_filename|>

var StateManager = function(){
	var state = contextObject.state;

	delete contextObject.state;

	this.getState=function(){
		return state;
	};
};
exports.api = {
	stateManager: new StateManager()
};


<|start_filename|>lib/sandbox.js<|end_filename|>
var _ = require('lodash'),
  net = require('net'),
  vm = require('vm'),
  BufferStream = require('bufferstream'),
  clone = require('clone');

function Sandbox(opts) {
  _.extend(this, {
    socket: '/tmp/sandcastle.sock'
  }, opts);
}

Sandbox.prototype.start = function() {
  var _this = this;
  var data = '';

  this.server = net.createServer(function(c) {

    var stream = new BufferStream({size:'flexible'});

    // script begin
    stream.split('\u0000\u0000', function(chunk) {
      _this.executeScript(c, chunk);
    });

    // recieved answer
    stream.split('\u0000', function (chunk) {
      _this.answerTask(c, chunk);
    });

    c.on('data', stream.write);
  });

  this.server.listen(this.socket, function() {
    console.log('sandbox server created'); // emit data so that sandcastle knows sandbox is created
  });

  this.server.on('error', function() {
    setTimeout(function() {
      _this.start();
    }, 500);
  });
};

Sandbox.prototype._sendError = function (connection, e, replaceStack) {
  connection.write(JSON.stringify({
      error: {
        message: e.message,
        stack: !replaceStack ? e.stack : e.stack.replace()
      }
    }) + '\u0000\u0000'); // exit/start separator
};

Sandbox.prototype.answerTask = function(connection, data) {
  var _this = this;

  try {
    var taskData = JSON.parse(data.toString()),
      taskName = taskData.task,
      onAnswerName = 'on' + taskName.charAt(0).toUpperCase() + taskName.slice(1) + 'Task';

    if (this._ctx.exports[onAnswerName]) {
      this._ctx.exports[onAnswerName](taskData.data);
    }
    else if (this._ctx.exports.onTask) {
      this._ctx.exports.onTask(taskName, taskData.data);
    }
  }
  catch (e) {
    _this._sendError(connection, e);
  }
};

Sandbox.prototype.executeScript = function(connection, data) {
  var contextObject = {
      runTask: function (taskName, options) {
        options = options || {};

        try {
          connection.write(JSON.stringify({
            task: taskName,
            options: options
          }) + '\u0000'); // task seperator
        } catch(e) {
          this._sendError(connection, e, false);
        }
      },
      exit: function(output) {
        try {
          connection.write(JSON.stringify(output) + '\u0000\u0000'); // exit/start separator
        } catch(e) {
          _this._sendError(connection, e, true);
        }
      }
    };

  try {
    var script = JSON.parse(data);


    // The trusted global variables.
    if (script.globals) {
      var globals = JSON.parse(script.globals);

      Object.keys(globals).forEach(function(key) {
        contextObject[key] = globals[key];
      });
    }

    // The trusted API.
    if (script.sourceAPI) {
      var api = eval(script.sourceAPI);

      Object.keys(api).forEach(function(key) {
        contextObject[key] = api[key];
      });
    }

    // recursively clone contextObject without prototype,
    // to prevent exploits using __defineGetter__, __defineSetter__.
    // https://github.com/bcoe/sandcastle/pull/21
    contextObject = clone(contextObject, true, Infinity, null);

    this._ctx = vm.createContext(contextObject);

    vm.runInNewContext( this.wrapForExecution(script.source, script.methodName), this._ctx);
  } catch (e) {
    this._sendError(connection, e, false);
  }

};

Sandbox.prototype.wrapForExecution = function(source, methodName) {
  return "var exports = Object.create(null);" + source + "\nexports." + methodName + "();";
};

exports.Sandbox = Sandbox;


<|start_filename|>examples/error.js<|end_filename|>
var SandCastle = require('../').SandCastle;

var sandcastle = new SandCastle();

var script = sandcastle.createScript("\
  exports.main = function() {\n\
    require('fs');\n\
  }\
");

script.on('exit', function(err, output) {
    console.log(err.message);
    console.log(err.stack);
});

script.run();

<|start_filename|>examples/multiple-functions.js<|end_filename|>
var async = require('async'),
  SandCastle = require('../lib').SandCastle,
  sandcastle = new SandCastle();

var script = sandcastle.createScript("\
  exports = {\
    foo: function() {\
      exit('Hello Foo!');\
    },\
    bar: function() {\
      exit('Hello Bar!');\
    },\
    hello: function() {\
      exit('Hey ' + name + ' Hello World!');\
    }\
  }\
");

script.on('timeout', function(methodName) {
  console.log(methodName);
});

script.on('exit', function(err, output, methodName) {
  console.log(methodName, output); // foo, bar, hello
  return cb();
});

var cb = null;

async.eachLimit(['foo', 'bar', 'hello'], 1, function(item, _cb) {
  cb = _cb;
  script.run(item, {name: 'Ben'});
});


<|start_filename|>examples/benchmark.js<|end_filename|>
var SandCastle = require('../lib').SandCastle,
  equal = require('assert').equal;

var sandcastle = new SandCastle({
  api: './examples/api.js', // We provide an API with the setTimeout function.,
  timeout: 2000
});

(function execute(n) {

  if (!n) {
    process.exit(0);
    return;
  }

  // Keep track of execution stats.
  var stats = {
    script1: 0,
    script2: 0,
    script3: 0
  };

  // A non-malicious script with a timeout.
  var script1 = sandcastle.createScript("\
    exports.main = function() {\
      setTimeout(function() {\
        exit('script1')\
      }, 10)\
    }\
  ");

  script1.on('exit', function(err, output) {
    stats[output] += 1;
  });

  script1.on('timeout', function() {
    // reschedule script1 when it times out.
    script1.run();
  });

  // a malicious script that loops infinitely.
  var script2 = sandcastle.createScript("\
    exports.main = function() {\
      while('script2') {};\
    }\
  ");

  script2.on('exit', function(err, output) {
    stats[output] += 1;
  });

  // A non-malicious script with a timeout.
  var script3 = sandcastle.createScript("\
    exports.main = function() {\
      setTimeout(function() {\
        exit('script3')\
      }, 1000)\
    }\
  ");

  script3.on('exit', function(err, output) {
    stats[output] += 1;
  });

  script3.on('timeout', function() {

    // reschedule script3 when it times out.

    script3.removeAllListeners('exit');
    script3.on('exit', function(err, output) {
      stats.script3 += 1;
      
      // Make sure the scripts were
      // executed the appropriate # of times.
      equal(stats.script1, 2);
      equal(stats.script2, 0);
      equal(stats.script3, 2);
      console.log('scripts executed the correct # of times', stats)
      setTimeout(function() {
        execute(n - 1);
      }, 0);
    });
    script3.run();
  });

  // Running script 1 and 3 will take.
  // about 2 seconds to complete.
  script1.run();
  script3.run();

  var id = setInterval(function() {
    if (script1.exited && script3.exited) {
      clearInterval(id);

      // both script1 and script 2 exited after their first execution.
      // next we will schedule them with a malicious script.

      script3.run();
      script1.run();
      // script2 is malicious and will cause the sandbox to shutdown
      // script1 and script3's timeout methods however cause them to be
      // rescheduled.
      script2.run();
    }
  }, 1000);

})(1000);

<|start_filename|>examples/hello.js<|end_filename|>
var SandCastle = require('../lib').SandCastle;

var sandcastle = new SandCastle();

var script = sandcastle.createScript("\
  exports.main = function() {\
    exit('Hello World!');\
  }\
");

script.on('exit', function(err, output) {
    console.log(output); // Hello World!
    process.exit(0);
});

script.run();


<|start_filename|>examples/api.js<|end_filename|>
var fs = require('fs');
var lodash = require('lodash')

exports.api = {
  extend: function () {
    lodash.extend.apply(this, arguments)
  },
  getFact: function(callback) {
    fs.readFile('./examples/example.txt', function (err, data) {
      if (err) throw err;
      callback(data.toString());
    });
  },
  setTimeout: function(callback, timeout) {
    setTimeout(callback, timeout);
  },
  callAdditional: function(callback) {
    anotherFunction(callback)
  },
  cwd: function() {
    return process.cwd();
  }
}
