sandbox 0.8.6 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/sandbox.mjs +3 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +27 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +46 -0
- package/package.json +32 -28
- package/.npmignore +0 -2
- package/README.md +0 -128
- package/UNLICENSE +0 -25
- package/build/Makefile +0 -350
- package/build/Release/.deps/Release/obj.target/sandbox/src/sandbox.o.d +0 -20
- package/build/Release/.deps/Release/sandbox.node.d +0 -1
- package/build/Release/linker.lock +0 -0
- package/build/Release/obj.target/sandbox/src/sandbox.o +0 -0
- package/build/Release/sandbox.node +0 -0
- package/build/binding.Makefile +0 -6
- package/build/config.gypi +0 -36
- package/build/gyp-mac-tool +0 -512
- package/build/sandbox.target.mk +0 -156
- package/example/example.js +0 -64
- package/lib/sandbox.js +0 -124
- package/lib/shovel.js +0 -113
- package/test/sandbox.js +0 -119
- package/tmp.js +0 -8
package/example/example.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
var Sandbox = require("../lib/sandbox")
|
|
2
|
-
, s = new Sandbox()
|
|
3
|
-
|
|
4
|
-
// Example 1 - Standard JS
|
|
5
|
-
s.run( "1 + 1", function( output ) {
|
|
6
|
-
console.log( "Example 1: " + output.result + "\n" )
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
// Example 2 - Something slightly more complex
|
|
10
|
-
s.run( "(function(name) { return 'Hi there, ' + name + '!'; })('Fabio')", function( output ) {
|
|
11
|
-
console.log( "Example 2: " + output.result + "\n" )
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
// Example 3 - Syntax error
|
|
15
|
-
s.run( "lol)hai", function( output ) {
|
|
16
|
-
console.log( "Example 3: " + output.result + "\n" )
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// Example 4 - Restricted code
|
|
20
|
-
s.run( "process.platform", function( output ) {
|
|
21
|
-
console.log( "Example 4: " + output.result + "\n" )
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
// Example 5 - Infinite loop
|
|
25
|
-
s.run( "while (true) {}", function( output ) {
|
|
26
|
-
console.log( "Example 5: " + output.result + "\n" )
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
// Example 6 - Caller Attack Failure
|
|
30
|
-
s.run( "(function foo() {return foo.caller.caller;})()", function( output ) {
|
|
31
|
-
console.log( "Example 6: " + output.result + "\n" )
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
// Example 7 - Argument Attack Failure
|
|
35
|
-
s.run( "(function foo() {return [].slice.call(foo.caller.arguments);})()", function( output ) {
|
|
36
|
-
console.log( "Example 7: " + output.result + "\n" )
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
// Example 8 - Type Coersion Attack Failure
|
|
40
|
-
s.run( "(function foo() {return {toJSON:function x(){return x.caller.caller.name}}})()", function( output ) {
|
|
41
|
-
console.log( "Example 8: " + output.result + "\n" )
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
// Example 9 - Global Attack Failure
|
|
45
|
-
s.run( "x=1;(function() {return this})().console.log.constructor('return this')()", function( output ) {
|
|
46
|
-
console.log( "Example 9: " + output.result + "\n" )
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
// Example 10 - Console Log
|
|
50
|
-
s.run( "var x = 5; console.log(x * x); x", function( output ) {
|
|
51
|
-
console.log( "Example 10: " + output.console + "\n" )
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
// Example 11 - IPC Messaging
|
|
55
|
-
s.run( "onmessage = function(message){ if (message === 'hello from outside') { postMessage('hello from inside'); };", function(output){
|
|
56
|
-
|
|
57
|
-
})
|
|
58
|
-
s.on('message', function(message){
|
|
59
|
-
console.log("Example 11: received message sent from inside the sandbox '" + message + "'\n")
|
|
60
|
-
});
|
|
61
|
-
var test_message = "hello from outside";
|
|
62
|
-
console.log("Example 11: sending message into the sandbox '" + test_message + "'");
|
|
63
|
-
s.postMessage(test_message);
|
|
64
|
-
|
package/lib/sandbox.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
//-----------------------------------------------------------------------------
|
|
2
|
-
// Init
|
|
3
|
-
//-----------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
var fs = require('fs');
|
|
6
|
-
var path = require('path');
|
|
7
|
-
var spawn = require('child_process').spawn;
|
|
8
|
-
var util = require('util');
|
|
9
|
-
var EventEmitter = require('events').EventEmitter;
|
|
10
|
-
|
|
11
|
-
//-----------------------------------------------------------------------------
|
|
12
|
-
// Constructor
|
|
13
|
-
//-----------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
function Sandbox(options) {
|
|
16
|
-
var self = this;
|
|
17
|
-
|
|
18
|
-
// message_queue is used to store messages that are meant to be sent
|
|
19
|
-
// to the sandbox before the sandbox is ready to process them
|
|
20
|
-
self._ready = false;
|
|
21
|
-
self._message_queue = [];
|
|
22
|
-
|
|
23
|
-
self.options = {
|
|
24
|
-
timeout: 500,
|
|
25
|
-
node: 'node',
|
|
26
|
-
shovel: path.join(__dirname, 'shovel.js')
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
self.info = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Make the Sandbox class an event emitter to handle messages
|
|
33
|
-
util.inherits(Sandbox, EventEmitter);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//-----------------------------------------------------------------------------
|
|
37
|
-
// Instance Methods
|
|
38
|
-
//-----------------------------------------------------------------------------
|
|
39
|
-
|
|
40
|
-
Sandbox.prototype.run = function(code, hollaback) {
|
|
41
|
-
var self = this;
|
|
42
|
-
var timer;
|
|
43
|
-
var stdout = '';
|
|
44
|
-
self.child = spawn(this.options.node, [this.options.shovel], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });
|
|
45
|
-
var output = function(data) {
|
|
46
|
-
if (!!data) {
|
|
47
|
-
stdout += data;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
if (typeof hollaback == 'undefined') {
|
|
52
|
-
hollaback = console.log;
|
|
53
|
-
} else {
|
|
54
|
-
hollaback = hollaback.bind(this);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Listen
|
|
58
|
-
self.child.stdout.on('data', output);
|
|
59
|
-
|
|
60
|
-
// Pass messages out from child process
|
|
61
|
-
// These messages can be handled by Sandbox.on('message', function(message){...});
|
|
62
|
-
self.child.on('message', function(message){
|
|
63
|
-
if (message === '__sandbox_inner_ready__') {
|
|
64
|
-
|
|
65
|
-
self.emit('ready');
|
|
66
|
-
self._ready = true;
|
|
67
|
-
|
|
68
|
-
// Process the _message_queue
|
|
69
|
-
while(self._message_queue.length > 0) {
|
|
70
|
-
self.postMessage(self._message_queue.shift());
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
} else {
|
|
74
|
-
self.emit('message', message);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
self.child.on('exit', function(code) {
|
|
79
|
-
clearTimeout(timer);
|
|
80
|
-
setImmediate(function(){
|
|
81
|
-
if (!stdout) {
|
|
82
|
-
hollaback({ result: 'Error', console: [] });
|
|
83
|
-
} else {
|
|
84
|
-
var ret;
|
|
85
|
-
try {
|
|
86
|
-
ret = JSON.parse(stdout);
|
|
87
|
-
} catch (e) {
|
|
88
|
-
ret = { result: 'JSON Error (data was "'+stdout+'")', console: [] }
|
|
89
|
-
}
|
|
90
|
-
hollaback(ret);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// Go
|
|
96
|
-
self.child.stdin.write(code);
|
|
97
|
-
self.child.stdin.end();
|
|
98
|
-
|
|
99
|
-
timer = setTimeout(function() {
|
|
100
|
-
self.child.stdout.removeListener('output', output);
|
|
101
|
-
stdout = JSON.stringify({ result: 'TimeoutError', console: [] });
|
|
102
|
-
self.child.kill('SIGKILL');
|
|
103
|
-
}, self.options.timeout);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// Send a message to the code running inside the sandbox
|
|
107
|
-
// This message will be passed to the sandboxed
|
|
108
|
-
// code's `onmessage` function, if defined.
|
|
109
|
-
// Messages posted before the sandbox is ready will be queued
|
|
110
|
-
Sandbox.prototype.postMessage = function(message) {
|
|
111
|
-
var self = this;
|
|
112
|
-
|
|
113
|
-
if (self._ready) {
|
|
114
|
-
self.child.send(message);
|
|
115
|
-
} else {
|
|
116
|
-
self._message_queue.push(message);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
//-----------------------------------------------------------------------------
|
|
121
|
-
// Export
|
|
122
|
-
//-----------------------------------------------------------------------------
|
|
123
|
-
|
|
124
|
-
module.exports = Sandbox;
|
package/lib/shovel.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
var util = require('util');
|
|
2
|
-
var vm = require('vm');
|
|
3
|
-
|
|
4
|
-
//-----------------------------------------------------------------------------
|
|
5
|
-
// Sandbox
|
|
6
|
-
//-----------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
var code = '';
|
|
9
|
-
var stdin = process.openStdin();
|
|
10
|
-
var result;
|
|
11
|
-
var console = [];
|
|
12
|
-
|
|
13
|
-
// Get code
|
|
14
|
-
stdin.on('data', function(data) {
|
|
15
|
-
code += data;
|
|
16
|
-
});
|
|
17
|
-
stdin.on('end', run);
|
|
18
|
-
|
|
19
|
-
function getSafeRunner() {
|
|
20
|
-
var global = this;
|
|
21
|
-
// Keep it outside of strict mode
|
|
22
|
-
function UserScript(str) {
|
|
23
|
-
// We want a global scoped function that has implicit returns.
|
|
24
|
-
return Function('return eval('+JSON.stringify(str+'')+')');
|
|
25
|
-
}
|
|
26
|
-
// place with a closure that is not exposed thanks to strict mode
|
|
27
|
-
return function run(comm, src) {
|
|
28
|
-
// stop argument / caller attacks
|
|
29
|
-
"use strict";
|
|
30
|
-
var send = function send(event) {
|
|
31
|
-
"use strict";
|
|
32
|
-
//
|
|
33
|
-
// All comm must be serialized properly to avoid attacks, JSON or XJSON
|
|
34
|
-
//
|
|
35
|
-
comm.send(event, JSON.stringify([].slice.call(arguments,1)));
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
global.print = send.bind(global, 'stdout');
|
|
39
|
-
global.console = { log: send.bind(global, 'stdout') };
|
|
40
|
-
global.process = {
|
|
41
|
-
stdout: { write: send.bind(global, 'stdout') }
|
|
42
|
-
};
|
|
43
|
-
global.postMessage = send.bind(global, 'message');
|
|
44
|
-
|
|
45
|
-
// This is where the user's source code is actually evaluated
|
|
46
|
-
var result = UserScript(src)();
|
|
47
|
-
send('end', result);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// Run code
|
|
52
|
-
function run() {
|
|
53
|
-
|
|
54
|
-
var context = vm.createContext();
|
|
55
|
-
var safeRunner = vm.runInContext('('+getSafeRunner.toString()+')()', context);
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
safeRunner({
|
|
59
|
-
send: function (event, value) {
|
|
60
|
-
"use strict";
|
|
61
|
-
|
|
62
|
-
switch (event) {
|
|
63
|
-
case 'stdout':
|
|
64
|
-
console.push(JSON.parse(value)[0]);
|
|
65
|
-
break;
|
|
66
|
-
case 'end':
|
|
67
|
-
result = JSON.parse(value)[0];
|
|
68
|
-
break;
|
|
69
|
-
case 'message':
|
|
70
|
-
process.send(JSON.parse(value)[0]);
|
|
71
|
-
break;
|
|
72
|
-
default:
|
|
73
|
-
throw new Error('Unknown event type');
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
exit: function(){
|
|
77
|
-
processExit();
|
|
78
|
-
}
|
|
79
|
-
}, code);
|
|
80
|
-
}
|
|
81
|
-
catch (e) {
|
|
82
|
-
result = e.name + ': ' + e.message;
|
|
83
|
-
// throw e;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
process.on('message', processMessageListener.bind(null, context));
|
|
87
|
-
|
|
88
|
-
process.send('__sandbox_inner_ready__');
|
|
89
|
-
|
|
90
|
-
// This will exit the process if onmessage was not defined
|
|
91
|
-
checkIfProcessFinished(context);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
function processMessageListener(context, message){
|
|
95
|
-
vm.runInContext('if (typeof onmessage === "function") { onmessage('+ JSON.stringify(String(message)) + '); }', context);
|
|
96
|
-
checkIfProcessFinished(context);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
function checkIfProcessFinished(context) {
|
|
100
|
-
if(vm.runInContext('typeof onmessage', context) !== 'function') {
|
|
101
|
-
processExit();
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
function processExit() {
|
|
106
|
-
process.removeListener('message', processMessageListener);
|
|
107
|
-
|
|
108
|
-
process.stdout.on('finish', function() {
|
|
109
|
-
process.exit(0);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
process.stdout.end(JSON.stringify({ result: util.inspect(result), console: console }));
|
|
113
|
-
};
|
package/test/sandbox.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
//-----------------------------------------------------------------------------
|
|
2
|
-
// Init
|
|
3
|
-
//-----------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
var should = require('should');
|
|
6
|
-
var sinon = require('sinon');
|
|
7
|
-
var Sandbox = require('../lib/sandbox');
|
|
8
|
-
|
|
9
|
-
//-----------------------------------------------------------------------------
|
|
10
|
-
// Tests
|
|
11
|
-
//-----------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
describe('Sandbox', function() {
|
|
14
|
-
var sb;
|
|
15
|
-
beforeEach(function(){
|
|
16
|
-
sb = new Sandbox();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should execute basic javascript', function(done) {
|
|
20
|
-
sb.run('1 + 1', function(output) {
|
|
21
|
-
output.result.should.eql('2');
|
|
22
|
-
done();
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should gracefully handle syntax errors', function(done) {
|
|
27
|
-
sb.run('hi )there', function(output) {
|
|
28
|
-
output.result.should.eql("'SyntaxError: Unexpected token )'");
|
|
29
|
-
done();
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should effectively prevent code from accessing node', function(done) {
|
|
34
|
-
sb.run('process.platform', function(output) {
|
|
35
|
-
output.result.should.eql("null");
|
|
36
|
-
done();
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should effectively prevent code from circumventing the sandbox', function(done) {
|
|
41
|
-
sb.run("var sys=require('sys'); sys.puts('Up in your fridge')", function(output) {
|
|
42
|
-
output.result.should.eql("'ReferenceError: require is not defined'");
|
|
43
|
-
done();
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should timeout on infinite loops', function(done) {
|
|
48
|
-
sb.run('while ( true ) {}', function(output) {
|
|
49
|
-
output.result.should.eql('TimeoutError');
|
|
50
|
-
done();
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should allow console output via `console.log`', function(done) {
|
|
55
|
-
sb.run('console.log(7); 42', function(output) {
|
|
56
|
-
output.result.should.eql('42');
|
|
57
|
-
output.console[0].should.eql(7);
|
|
58
|
-
done();
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('should allow console output via `print`', function(done) {
|
|
63
|
-
sb.run('print(7); 42', function(output) {
|
|
64
|
-
output.result.should.eql('42');
|
|
65
|
-
output.console[0].should.eql(7);
|
|
66
|
-
done();
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should maintain the order of sync. console output', function(done) {
|
|
71
|
-
sb.run('console.log("first"); console.log("second"); 42', function(output) {
|
|
72
|
-
output.result.should.eql('42');
|
|
73
|
-
output.console[0].should.eql('first');
|
|
74
|
-
output.console[1].should.eql('second');
|
|
75
|
-
done();
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should expose the postMessage command to the sandboxed code', function(done){
|
|
80
|
-
var messageHandler = sinon.spy();
|
|
81
|
-
sb.on('message', messageHandler);
|
|
82
|
-
sb.run('postMessage("Hello World!");', function(output){
|
|
83
|
-
messageHandler.calledOnce.should.eql(true);
|
|
84
|
-
messageHandler.calledWith('Hello World!').should.eql(true);
|
|
85
|
-
done();
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should allow sandboxed code to receive messages sent by postMessage from the outside by overwriting the onmessage function', function(done){
|
|
90
|
-
var messageHandler = sinon.spy();
|
|
91
|
-
sb.on('message', messageHandler);
|
|
92
|
-
sb.on('ready', function () {
|
|
93
|
-
sb.postMessage('Hello World!');
|
|
94
|
-
});
|
|
95
|
-
sb.run('onmessage = function (msg) { postMessage(msg); };', function(output) {
|
|
96
|
-
messageHandler.callCount.should.eql(1);
|
|
97
|
-
messageHandler.calledWith('Hello World!').should.eql(true);
|
|
98
|
-
done();
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should queue messages posted before the sandbox is ready and process them once it is', function(done){
|
|
103
|
-
var messageHandler = sinon.spy();
|
|
104
|
-
var num_messages_sent = 0;
|
|
105
|
-
var interval = setInterval(function(){
|
|
106
|
-
sb.postMessage(++num_messages_sent);
|
|
107
|
-
}, 1);
|
|
108
|
-
sb.on('message', messageHandler);
|
|
109
|
-
sb.run('onmessage = function (msg) { postMessage(msg); };', function(output) {
|
|
110
|
-
messageHandler.callCount.should.eql(num_messages_sent);
|
|
111
|
-
num_messages_sent.should.be.greaterThan(0);
|
|
112
|
-
done();
|
|
113
|
-
});
|
|
114
|
-
sb.on('ready', function(){
|
|
115
|
-
clearInterval(interval);
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
});
|
package/tmp.js
DELETED