Skip to main content

场景:需求前端有一个编辑器,可提供执行代码的功能,这个执行的沙箱需要注意什么,怎么实现

沙箱中执行代码的实现方式大致有三种:

  1. eval直接运行,
  2. new Function,通过将代码序列化作为参数传入,
  3. 将输入代码序列化之后传到serverless服务上,由后端沙箱环境执行之后返回页面

通常这三种方式里面使用第三种比较安全,因为node端有比较完善的沙箱安全机制和模块加载机制。

构建沙箱需要注意的问题大致下面几种:

  1. 模块加载功能,使用serverless的时候可以使用现成的模块,如require-from-string来解决,其他两种方案的话则需要写一个模块加载器,使用AMD的模块加载方案
  2. 隔离对象访问,为了防止用户恶意攻击编辑器页面,我们需要吧用户运行的代码隔离起来运行,我想到的方案是使用webworker,专门运行用户编辑的代码
  3. 防止耗cpu的操作比如while(true)死循环,这类问题的话可以设置超时,超过运行时间直接抛弃。如果超时无法设置的话就使用刚才的webworker,在主进程中直接终止worker运行
  4. 防止内存泄漏,如果代码不断的打印日志的话,很快就把浏览器卡死了,这类问题比较难控制,可以根据经验对日志进行监控,超过一定返回的话就不再打印。限制模块里面引用第三方的库,或者对常用库针对性处理。对可能造成内存泄漏的函数进行重写。

MDN:执行代码的沙箱

https://interactive-examples.mdn.mozilla.net/pages/js/array-splice.html

在线运行代码的网址除了codepen比较好之后还有一个runkit:

https://runkit.com/embed/6u361dpz17bh

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