浅析JS 沙箱(JavaScript Sandbox)

317 阅读4分钟

在前端领域,JS 沙箱(JavaScript Sandbox)  是一种安全机制,用于隔离和限制代码的执行环境,防止不受信任的脚本访问或修改外部资源(如全局变量、DOM、本地存储等),从而避免安全风险或意外冲突。

核心作用

JS 沙箱的核心是创建一个「隔离环境」,让内部代码只能在有限范围内运行,主要解决:

  1. 全局变量污染:防止沙箱内的变量 / 函数覆盖外部全局对象(如 window);
  2. 权限控制:限制沙箱内代码对敏感 API 的访问(如 localStoragefetch);
  3. 环境隔离:在同一页面中安全运行多个独立脚本(如微应用、第三方插件)。

常见实现方式

根据隔离强度和使用场景,JS 沙箱有多种实现方案:

1. with + 代理对象(Proxy)

通过 with 语法改变作用域链,并结合 Proxy 拦截对象访问,是轻量级沙箱的常用方式。

javascript

运行

function createSandbox() {
  // 沙箱内的全局对象(模拟 window)
  const sandboxGlobal = new Proxy({}, {
    get(target, key) {
      // 优先从沙箱内获取,不存在则从外部 window 获取
      return key in target ? target[key] : window[key];
    },
    set(target, key, value) {
      // 只允许修改沙箱内的对象,不污染外部 window
      target[key] = value;
      return true;
    }
  });

  // 执行沙箱内代码
  function runCode(code) {
    with (sandboxGlobal) {
      // 使用 eval 执行代码,此时作用域为 sandboxGlobal
      eval(code);
    }
  }

  return { runCode, sandboxGlobal };
}

// 使用沙箱
const sandbox = createSandbox();
sandbox.runCode(`
  a = 1; // 存储在 sandboxGlobal 中,不影响外部 window.a
  console.log(b); // 若外部 window.b 存在则输出,否则 undefined
`);
console.log(window.a); // undefined(未被污染)
console.log(sandbox.sandboxGlobal.a); // 1(沙箱内变量)

缺点with 语法性能较差,且无法完全阻止对外部全局对象的访问(如通过 window.xxx 直接访问)。

2. iframe 沙箱

利用浏览器原生的 iframe 标签创建独立的全局环境,隔离性最强。

html

预览

<!-- 主页面 -->
<iframe id="sandboxFrame" sandbox="allow-scripts"></iframe>

<script>
  const iframe = document.getElementById('sandboxFrame');
  const iframeWindow = iframe.contentWindow;

  // 向 iframe 注入代码并执行
  function runInIframe(code) {
    const script = iframeWindow.document.createElement('script');
    script.textContent = code;
    iframeWindow.document.body.appendChild(script);
  }

  // 执行不受信任的代码
  runInIframe(`
    a = 1; // 仅在 iframe 的 window 中有效
    console.log(window.a); // 1(iframe 内)
    // 受 sandbox 属性限制,无法访问父页面或本地存储
  `);

  console.log(window.a); // undefined(主页面不受影响)
</script>

特点

  • 完全独立的全局环境,隔离性最好;
  • 通过 sandbox 属性可精细控制权限(如 allow-scripts 允许执行脚本,allow-same-origin 允许同源访问等);
  • 缺点是通信成本高(需通过 postMessage),性能开销较大。

3. 快照沙箱(Snapshot Sandbox)

适用于单线程环境(如微前端),通过记录全局对象的「快照」,在沙箱激活 / 销毁时恢复状态。

javascript

运行

class SnapshotSandbox {
  constructor() {
    this.snapshot = new Map(); // 存储全局对象初始状态
    this.modified = new Map(); // 存储沙箱内修改的属性
    this.active = false;
  }

  // 激活沙箱:记录当前全局状态快照
  activate() {
    this.snapshot.clear();
    // 记录 window 上的关键属性(如 document、location 等)
    ['a', 'b', 'c'].forEach(key => {
      this.snapshot.set(key, window[key]);
    });
    // 恢复之前修改的属性
    this.modified.forEach((value, key) => {
      window[key] = value;
    });
    this.active = true;
  }

  // 销毁沙箱:恢复全局状态到初始快照
  deactivate() {
    this.modified.clear();
    // 记录沙箱内修改的属性,并恢复初始值
    ['a', 'b', 'c'].forEach(key => {
      if (window[key] !== this.snapshot.get(key)) {
        this.modified.set(key, window[key]);
        window[key] = this.snapshot.get(key); // 恢复快照值
      }
    });
    this.active = false;
  }
}

// 使用示例
const sandbox = new SnapshotSandbox();
sandbox.activate();
window.a = 1; // 在沙箱激活时修改
sandbox.deactivate();
console.log(window.a); // 恢复为初始值(如 undefined)

适用场景:微前端中切换子应用时,隔离不同应用对全局变量的修改(如 qiankun 框架早期使用此方案)。

4. VM 模块(Node.js 环境)

在 Node.js 中,可通过内置的 vm 模块创建沙箱,隔离代码执行环境:

javascript

运行

const vm = require('vm');

// 创建沙箱上下文
const sandbox = { x: 1, y: 2 };
const context = new vm.createContext(sandbox);

// 在沙箱中执行代码
const code = 'x + y;';
const result = vm.runInContext(code, context);
console.log(result); // 3(沙箱内计算结果)
console.log(sandbox.x); // 1(沙箱内变量未被污染)

典型应用场景

  1. 微前端框架:如 @micro-zoe/micro-appqiankun 等,通过沙箱隔离各子应用的全局变量和样式,避免冲突;
  2. 代码在线编辑器:如 CodeSandbox、JSFiddle,使用沙箱安全执行用户输入的代码;
  3. 第三方插件 / 广告:在页面中嵌入第三方脚本时,通过沙箱限制其权限,防止恶意行为;
  4. 测试环境:隔离测试用例,避免测试代码影响全局环境。

总结

JS 沙箱的核心是「隔离」与「限制」,不同实现方案在隔离强度、性能和易用性上各有取舍:

  • 轻量场景(如微应用)可选择 Proxy 或快照沙箱;
  • 高安全需求(如执行未知代码)优先使用 iframe 或 Node.js 的 vm 模块。
    实际开发中需根据具体场景选择合适的方案,平衡安全性和性能。