场景:需求前端有一个编辑器,可提供执行代码的功能,这个执行的沙箱需要注意什么,怎么实现
沙箱中执行代码的实现方式大致有三种:
- eval直接运行,
- new Function,通过将代码序列化作为参数传入,
- 将输入代码序列化之后传到serverless服务上,由后端沙箱环境执行之后返回页面
通常这三种方式里面使用第三种比较安全,因为node端有比较完善的沙箱安全机制和模块加载机制。
构建沙箱需要注意的问题大致下面几种:
- 模块加载功能,使用serverless的时候可以使用现成的模块,如require-from-string来解决,其他两种方案的话则需要写一个模块加载器,使用AMD的模块加载方案
- 隔离对象访问,为了防止用户恶意攻击编辑器页面,我们需要吧用户运行的代码隔离起来运行,我想到的方案是使用webworker,专门运行用户编辑的代码
- 防止耗cpu的操作比如while(true)死循环,这类问题的话可以设置超时,超过运行时间直接抛弃。如果超时无法设置的话就使用刚才的webworker,在主进程中直接终止worker运行
- 防止内存泄漏,如果代码不断的打印日志的话,很快就把浏览器卡死了,这类问题比较难控制,可以根据经验对日志进行监控,超过一定返回的话就不再打印。限制模块里面引用第三方的库,或者对常用库针对性处理。对可能造成内存泄漏的函数进行重写。
MDN:执行代码的沙箱
https://interactive-examples.mdn.mozilla.net/pages/js/array-splice.html
在线运行代码的网址除了codepen比较好之后还有一个runkit:
otehr:
- iframe 嵌入执行
- nodejs 原生模块 vm 做
- with + new Function
Code :
function compileCode(src){ src = `with(exposeObj){${src}}` return new Function('exposeObj',src)}
function proxyObj(originObj){ return new Proxy(originObj,{ has:(target,key)=>{ if(["console","Math","Date"].indexOf(key) >= 0){ return target[key] } return target[key] } })}
function createSandbox(src,obj){ let proxy = proxyObj(obj) compileCode(src).call(proxy,proxy)}
// testlet test = {say:1}createSandbox("say='hello sanbox';console.log(say)",test) //hello sanbox