导读:本期聚焦于半糖创作的《在 Node.js 中,如何利用 vm 模块在沙箱环境中执行不可信代码?》,敬请观看详情。在 Node.js 开发中,执行不可信代码存在安全风险,vm 模块提供了沙箱环境能力来隔离代码执行。本文介绍 vm 模块的核心 API 使用方法,讲解如何构建安全的沙箱上下文,限制不可信代码的访问权限,避免其操作宿主环境的全局对象、文件系统或网络资源。同时会说明 vm 模块的安全边界,分析常见的配置误区,给出完整的代码示例,帮助开发者正确利用 vm 模块实现不可信代码的安全执行,降低代码注入带来的安全隐患。

在 Node.js 中,vm 模块作为官方内置模块,提供了一种在独立上下文环境中执行 JavaScript 代码的机制。所谓独立上下文,指的是代码执行时的全局对象与宿主环境互相隔离,宿主进程中的大部分全局能力不会直接暴露给被执行的代码。通过这种方式,我们可以在一定程度上安全地运行来自用户输入、配置文件或第三方服务的不可信代码片段,同时降低其对主进程造成破坏的风险。

vm 模块核心 API 与基础执行流程

在使用 vm 模块之前,需要先理解它的几个关键方法。vm.createContext 负责将一个普通 JavaScript 对象转换为上下文对象,这个对象会成为后续代码执行时的全局对象。默认情况下,新创建的上下文不会包含 Node.js 的 process、require、Buffer 等常见全局变量,因此不可信代码无法直接通过这些接口访问文件系统或网络。

vm.runInContext 方法接收代码字符串、上下文对象以及可选配置,并返回代码最后的表达式结果。vm.runInNewContext 则整合了创建上下文和执行代码两个步骤,适合一次性执行场景。此外,vm.Script 类可以预编译代码,便于在多个上下文中重复执行,提高效率。

下面通过一个基础示例展示如何创建沙箱并执行不可信代码:

const vm = require('vm');

// 定义沙箱上下文,只暴露必要的变量
const sandbox = {
  a: 10,
  b: 20,
  console: console
};

// 将普通对象转换为沙箱上下文
vm.createContext(sandbox);

// 不可信代码字符串
const untrustedCode = `
  const total = a + b;
  console.log('沙箱中的计算结果:', total);
  total;
`;

// 在沙箱中执行代码
const result = vm.runInContext(untrustedCode, sandbox);
console.log('宿主进程获取返回值:', result);

构建安全的沙箱上下文

仅仅创建一个空上下文并不足以保证安全,开发者还需要对上下文内容和执行选项进行严格控制。沙箱的安全性很大程度上取决于挂载到上下文中的对象是否包含危险能力。如果错误地将 processrequireBuffer 等核心对象放入沙箱,不可信代码就可能通过它们读取文件、创建子进程甚至退出宿主进程。

最小权限原则是设计安全沙箱的核心思路:只向不可信代码提供完成其任务所必需的 API,不提供任何多余的接口。例如,如果沙箱代码只需要进行数学运算,就不应该提供任何与输入输出相关的能力。同时,对于传入沙箱的数据必须进行严格的类型检查和范围校验,避免不可信代码通过构造特殊值触发原型链污染等问题。

限制全局对象访问

在创建上下文时,不要将宿主环境的全局对象直接复制进去。比较安全的做法是显式列出允许访问的对象,例如只提供 Math、JSON 等纯计算或数据转换能力。任何涉及文件、网络、进程、模块加载的对象都不应出现在沙箱中。

const vm = require('vm');

// 仅提供纯计算所需的对象
const safeSandbox = {
  Math: Math,
  parseInt: parseInt
};

vm.createContext(safeSandbox);

const code = `
  const num = parseInt('128', 10);
  const squared = Math.pow(num, 2);
  squared;
`;

const output = vm.runInContext(code, safeSandbox);
console.log('安全沙箱计算结果:', output);

按需提供有限 API

如果不可信代码确有输出日志的需求,也不应该直接把宿主 console 对象原样传入,而应提供经过包装的、只包含特定方法的对象。这样既能满足调试需求,又能防止不可信代码调用 console 的其他方法或通过 console 访问宿主环境。

const vm = require('vm');

// 包装 console,仅暴露 log 和 error 方法
const safeConsole = {
  log: (...args) => console.log('[沙箱输出]', ...args),
  error: (...args) => console.error('[沙箱错误]', ...args)
};

const sandbox = {
  console: safeConsole
};

vm.createContext(sandbox);

const code = `
  console.log('这是一条来自沙箱的消息');
  const result = 6 * 7;
  result;
`;

const result = vm.runInContext(code, sandbox);
console.log('获取到的结果:', result);

设置超时时间

不可信代码可能包含死循环或长时间运行的计算,如果不加限制,宿主进程会被阻塞。vm 模块的 runInContext 方法支持通过 options 参数中的 timeout 字段设置执行超时时间,单位为毫秒。当代码执行超过设定时间后,Node.js 会抛出错误并中断执行。

const vm = require('vm');

const sandbox = {};
vm.createContext(sandbox);

const infiniteLoopCode = `
  while (true) {
    // 模拟死循环
  }
`;

try {
  // 设置 1000 毫秒超时
  vm.runInContext(infiniteLoopCode, sandbox, { timeout: 1000 });
} catch (err) {
  console.log('代码执行超时:', err.message);
}

使用 vm.Script 预编译与完整安全执行

当同一段不可信代码

当同一段不可信代码需要被反复执行时,可以先用 vm.Script 将源码编译成脚本对象,再通过 runInContext 在指定上下文中运行。预编译能避免重复解析,同时可以为每次执行单独设置超时时间,适合需要批量或周期性执行沙箱代码的场景。

const vm = require('vm');

const sandbox = {};
vm.createContext(sandbox);

const script = new vm.Script(`
  let sum = 0;
  for (let i = 1; i <= 100; i++) {
    sum += i;
  }
  sum;
`);

const result = script.runInContext(sandbox, { timeout: 500 });
console.log('第一次执行结果:', result);

const result2 = script.runInContext(sandbox, { timeout: 500 });
console.log('第二次执行结果:', result2);

由于 runInContext 会复用传入的上下文对象,如果脚本中使用了全局变量,多次执行之间会互相影响。例如,把计数器放在 globalThis 上时,第二次执行会看到上一次修改后的值。若需要每次执行都从干净状态开始,就应为每次调用创建新的上下文。

const vm = require('vm');

const script = new vm.Script(`
  globalThis.count = (globalThis.count || 0) + 1;
  globalThis.count;
`);

function runInFreshContext() {
  const sandbox = {};
  vm.createContext(sandbox);
  return script.runInContext(sandbox);
}

console.log(runInFreshContext()); // 1
console.log(runInFreshContext()); // 1

除超时与上下文隔离外,还可以对沙箱中的内建对象做加固。例如冻结 Object.prototypeArray.prototype 等原型对象,可以在一定程度上阻止恶意代码篡改基础方法。但这类加固并不构成绝对安全边界,原型链逃逸和异步资源消耗仍可能发生,因此只能作为辅助手段。

如果要在生产环境中执行真正不受信任的代码,仅靠 vm 模块是不够的。vm 在同一进程内运行代码,无法彻底隔离 CPU 时间和内存占用,timeout 也只能中断同步执行。更稳妥的做法是使用 worker_threads 将不可信代码放入独立线程,或通过子进程、容器等方式实现操作系统级隔离。这样可以在任务超时或崩溃时直接终止执行单元,不影响主进程。

总体而言,Node.js 的 vm 模块提供了一种轻量级的代码执行隔离方案,适合运行可信但动态生成的脚本、配置表达式或测试代码。通过 createContextrunInContexttimeout 参数,可以控制沙箱全局环境与执行时长;预编译脚本则能提升重复执行效率。但若代码来源完全不可信,务必结合 worker_threads、子进程或容器等额外隔离层级,才能真正实现安全沙箱。

Node.jsvm_modulesandboxuntrusted_code修改时间:2026-07-19 15:03:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。