如何创建一个沙箱环境
new Function
new Function 构建的函数内部不可以访问闭包,不能访问局部变量,只可以访问全局变量
// 闭包
function aa (){
let name = 'jack'
return function(){
let str = "console.log('Hello' + name +'!') ; console.log(typeof name)";
let func = new Function(str);
func()
}
}
let bb = aa()
bb()
// 结果
// Hello!
//string
// 局部变量
function aa (){
let name = 'jack'
let str = "console.log('Hello' + name +'!') ; console.log(typeof name)";
let func = new Function(str);
func()
}
aa()
// 结果
// Hello!
//string
// 全局变量
let name = 'Bob'
function aa (){
let str = "console.log('Hello' + name +'!') ; console.log(typeof name)";
let func = new Function(str);
func()
}
aa()
// 结果
// HelloBob!
//string
let name = 'Bob'
function aa (){
return function(){
let str = "console.log('Hello' + name +'!') ; console.log(typeof name)";
let func = new Function(str);
func()
}
}
let bb = aa()
bb()
// 结果
// HelloBob!
//string
with
with传进的对象,会被放到包裹代码的原型链顶部, in操作符
function aa(a,b){
with(b){
console.log(a)
}
}
aa(1,{a: 2})
// 2
proxy
借助proxy,可以对in操作进行劫持 用例搬自MDN
const handler1 = {
has(target, key) { // 对in操作符的一个钩子
if (key[0] === '_') {
return false;
}
return key in target;
}
};
const monster1 = {
_secret: 'easily scared',
eyeCount: 4
};
const proxy1 = new Proxy(monster1, handler1);
console.log('eyeCount' in proxy1);
// expected output: true
console.log('_secret' in proxy1);
// expected output: false
console.log('_secret' in monster1);
// expected output: true
new Function + with + Proxy
new Function + with + Proxy 实现一个不能访问全局,局部,闭包,只能访问传入变量的沙箱
function sandbox(){
let obj = { require: 'this is require', export: 'this is export' }
const proxyObj = new Proxy(obj, {
has(target, key){
return true
},
get(target, key){
return Reflect.get(target, key)
}
})
let str = `with(obj){ return obj }`
// 这里面不能用console.log,因为我们传入的对象中并没有console对象
// 此时的with里面的代码只能拿到我们传入的proxyObj中拥有的属性的值
const func = new Function('objs', `with(objs){ return require }`)
return func(proxyObj)
}
let res = sandbox()
console.log(res)
// expexted output: this is require
Common Js 的简单实现
Common Js的特点
- commonJs的引用是对值的引用,是执行后的文件
- commonJs重复引用文件不会重复执行文件,说明被引用的文件结果会被存储起来,再次引用时直接拿值
- exports导出的对象是一个对象
- module.exports导出的是定义的本身
Common Js的实现
- 实现一个new Function + with + Proxy 配合的沙箱
- 定义一个有exports 和 require 属性的对象, 把这个对象作为参数传进沙箱
- 拿到目标文件的代码,放进with中执行
- 返回执行后的结果
class Module {
constructor(moduleName, source){
this.exports = {}
this.moduleName = moduleName
this.$cacheModule = new Map()
this.$source = source
}
/**
* @param {string} module 模块的名称: 路径
* @param {string} source 文件源代码
*/
require = (moduleName, source) => {
if(this.$cacheModule.has(moduleName)){
return this.$cacheModule.get(moduleName).exports
}
const module = new Module(moduleName, source)
this.$cacheModule.set(moduleName, module)
const exports = this.compile(module, source)
return exports
}
compile = (module, source) => {
const compiler = this.$runInThisContext(this.$wrap(source))({})
compiler.call(module, module, module.exports, this.require)
return module.exports
}
$wrap = (code) => {
const wrapper = [
'return (function (module, exports, require){',
'\n})'
]
return wrapper[0] + code + wrapper[1]
}
$runInThisContext = (code, whiteList=['console']) => {
const func = new Function('sandbox', `with(sandbox){ ${code} }`)
return function(sandbox){
if(!sandbox || typeof sandbox !== 'object'){
throw new Error('sandbox paramter must be an object')
}
const proxyObject = new Proxy(sandbox, {
has(target, key){
// 将consosle对象放进白名单,这样就会从全局对象中找console
if(!whiteList.includes(key)){
return true
}
},
get(target, key, receiver){
if(key === Symbol.unscopables){
return void 0
}
return Reflect.get(target, key, receiver)
}
})
return func(proxyObject)
}
}
}
const m = new Module()
const sourceCodeFromAModule = `
const b = require('b.js', 'exports.action = function(){ console.log("execute action from B module successfully")}')
b.action()
`
m.require('a.js', sourceCodeFromAModule)
// expexted output: execute action from B module successfully