nae-sandbox
Version:
Sandbox Environment for Node App Engine.
260 lines (214 loc) • 6.85 kB
JavaScript
/*!
* nae-sandbox - module.js
*
* Copyright(c) nae team and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
;
/**
* Module dependencies.
*/
var debug = require('debug')('nae:sandbox_module');
var util = require('util');
var Module = require('module').Module;
var path = require('path');
var runInThisContext = require('vm').runInThisContext;
var runInNewContext = require('vm').runInNewContext;
var assert = require('assert').ok;
var _disableModules = {};
var _nativeModules = {};
var _enableAddons = false;
var _limitRootDir = '/';
var _cacheModules = {};
var _nativeNames = [
'fs', 'path', 'os', 'tty',
'readline', 'stream', 'events',
'net', 'http', 'https', 'domain', 'dns', 'dgram',
'zlib', 'tls', 'crypto', 'buffer',
'util', 'assert', 'querystring',
'string_decoder',
'url',
];
var _extendGlobals = {};
// See: https://github.com/joyent/node/issues/1707#issuecomment-2106309
function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function initConfig() {
_cacheModules = {};
_disableModules = {};
_nativeModules = {};
for (var i = 0; i < _nativeNames.length; i++) {
var name = _nativeNames[i];
_nativeModules[name] = require(name);
}
_enableAddons = false;
_limitRootDir = '/';
}
/**
* Config Sandbox modules
*
* @param {Object} [options]
* - {Array} [disableModules] disable module names, e.g.: `['child_process', 'repl']`
* - {Object} modules configurable modules, e.g.: `{fs: fs2, net: net2}`
* - {String} limitRoot limit app root dir, e.g.: `/home/apps/foo`
* - {Boolean} enableAddons enable C/C++ addons or not, default is `false`
*/
exports.config = function (options) {
initConfig();
options = options || {};
if (options.disableModules && options.disableModules.length) {
for (var i = 0; i < options.disableModules.length; i++) {
_disableModules[options.disableModules[i]] = true;
}
}
if (options.modules) {
for (var k in options.modules) {
_nativeModules[k] = options.modules[k];
}
}
if (options.globals) {
for (var j in options.globals) {
_extendGlobals[j] = options.globals[j];
}
}
_enableAddons = options.enableAddons === true;
_limitRootDir = options.limitRoot || '/';
// make sure limit root dir end withs '/'
_limitRootDir = _limitRootDir.replace(/\/+$/g, '') + '/';
};
exports._resolveLookupPaths = Module._resolveLookupPaths;
exports._findPath = Module._findPath;
exports._resolveFilename = function (request, parent) {
debug('SandboxModule._resolveFilename %s parent: %s', request, parent && parent.id);
if (hasOwnProperty(_disableModules, request)) {
var err = new Error("Disable module '" + request + "'");
err.code = 'MODULE_DISABLE';
throw err;
}
if (hasOwnProperty(_nativeModules, request)) {
return request;
}
var resolvedModule = exports._resolveLookupPaths(request, parent);
var id = resolvedModule[0];
var paths = resolvedModule[1];
var limitPaths = [];
for (var i = 0; i < paths.length; i++) {
var basePath = path.resolve(paths[i], request);
if (basePath.indexOf(_limitRootDir) === 0) {
limitPaths.push(paths[i]);
}
}
// look up the filename first, since that's the cache key.
debug('SandboxModule._resolveFilename looking for %j in %j, orgin paths: %j',
request, limitPaths, paths);
var filename = exports._findPath(request, limitPaths);
if (!filename) {
var err = new Error("Cannot find module '" + request + "'");
err.code = 'MODULE_NOT_FOUND';
throw err;
}
if (!_enableAddons) {
var extname = path.extname(filename).toLowerCase();
if (extname === '.node') {
var err = new Error("Disable addons module '" + request + "'");
err.code = 'MODULE_DISABLE';
throw err;
}
}
return filename;
};
exports._load = function (request, parent, isMain) {
debug('SandboxModule._load %s parent: %s', request, parent && parent.id);
var filename = exports._resolveFilename(request, parent);
// check native modules first
if (hasOwnProperty(_nativeModules, filename)) {
debug('SandboxModule._load native module %s', request);
return _nativeModules[filename];
}
var cachedModule = _cacheModules[filename];
if (cachedModule) {
debug('SandboxModule._load cache module %s', request);
return cachedModule.exports;
}
var module = new SandboxModule(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
_cacheModules[filename] = module;
var hadException = true;
try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete _cacheModules[filename];
}
}
return module.exports;
};
function SandboxModule(id, parent) {
Module.call(this, id, parent);
// should not let sandbox inside module access super_
this.constructor.super_ = null;
}
util.inherits(SandboxModule, Module);
exports.SandboxModule = SandboxModule;
SandboxModule.prototype.require = function (path) {
assert(typeof path === 'string', 'path must be a string');
assert(path, 'missing path');
return exports._load(path, this);
};
SandboxModule.prototype._compile = function (content, filename) {
var self = this;
// remove shebang
content = content.replace(/^\#\!.*/, '');
var _require = self.require;
function require(path) {
return _require.call(self, path);
}
require.resolve = function(request) {
return exports._resolveFilename(request, self);
};
Object.defineProperty(require, 'paths', { get: function() {
throw new Error('require.paths is removed. Use ' +
'node_modules folders, or the NODE_PATH ' +
'environment variable instead.');
}});
require.main = process.mainModule;
// Enable support to add extra extension types
require.extensions = Module._extensions;
require.registerExtension = function() {
throw new Error('require.registerExtension() removed. Use ' +
'require.extensions instead.');
};
require.cache = _cacheModules;
debug('self._compile() %s', self.id);
var sandbox = {
'require' : require,
'exports' : self.exports,
'__filename' : filename,
'__dirname' : path.dirname(filename),
'module' : self,
'root' : root,
};
for (var k in global) {
sandbox[k] = global[k];
}
// Global variables in current process are polluted
for (var j in _extendGlobals) {
debug('extend global variable %s', j);
sandbox[j] = _extendGlobals[j];
}
sandbox.global = sandbox;
return runInNewContext(content, sandbox, { filename: filename });
};
// bootstrap main module.
exports.runMain = function (main) {
exports._load(main, null, true);
// process._tickCallback();
};