eval不适合受控环境下的动态指令执行,new Function虽作用域更可控但必须配合作用域隔离、输入校验和运行时限制,否则两者均会泄露敏感数据或调用任意全局API。

直接说结论:eval 不适合用于受控环境下的动态指令执行,new Function 是更可控的起点,但必须配合作用域隔离、输入校验和运行时限制——否则两者都会泄露 document.cookie、localStorage 或调用任意全局 API。
为什么 eval 在浏览器里根本不能算“受控”
eval 会完全继承当前执行上下文,包括闭包变量、函数参数、this 绑定,甚至能读写外层 let 变量。它不创建新作用域,只“钻进”现有作用域链最深一层。
常见翻车现象:
- 用户传入
"localStorage.setItem('x', 'hacked')",直接污染主页面存储 - 在闭包函数中调用
eval("password"),意外暴露敏感局部变量 - 压缩工具(如 terser)无法安全混淆含
eval的代码,常被误删或破坏
即使加了 "use strict",eval 依然能访问外层所有可访问标识符——严格模式只限制 with 和静默失败,不限制作用域穿透。
new Function 是起点,不是终点
new Function 创建的函数拥有独立作用域链,只链接到全局(或模块)作用域,不继承调用者的局部变量。这是它比 eval 更可控的根本原因。
但默认仍危险,因为:
- 它默认能访问
window、document、fetch等全部全局对象 - 若未显式声明
"use strict",内部仍可使用with、arguments.callee等非严格特性 - 每次调用都生成新函数,高频使用会触发 V8 的 deopt,影响性能
最小安全封装示例:
function safeRun(script) {
const fn = new Function('"use strict";', 'return (' + script + ');');
return fn();
}
注意:script 必须是纯表达式(如 "a + b"),不能含语句;若需支持语句,得包裹成 IIFE 并确保末尾有明确返回值。
真正受控的关键:切断对全局对象的访问
仅靠 new Function 远不够。必须主动剥离或代理全局对象,否则 script 仍可执行 fetch('/api/user') 或 alert(1)。
可行做法:
- 用
Object.freeze冻结self上关键属性(但无法阻止新增属性) - 在 Web Worker 中运行,天然无 DOM、无
window,但需序列化通信 - 用
<iframe sandbox="allow-scripts"></iframe>加载空页面,通过contentWindow注入受限上下文(注意 CSP 可能拦截eval) - 对不可信输入,必须走后端沙箱(如 Node.js 的
vm2或isolated-vm)
前端轻量级隔离的一个实操技巧:用 Proxy 拦截全局访问,只放行白名单方法:
const safeGlobal = new Proxy(globalThis, {
get(target, prop) {
if (['Math', 'JSON', 'parseInt', 'parseFloat'].includes(prop)) {
return target[prop];
}
throw new Error(`Access denied to ${prop}`);
}
});
// 然后把 safeGlobal 作为 this 传入 new Function 构造的函数
容易被忽略的兼容性与调试陷阱
很多方案在开发时跑通,上线后出问题,根源常在这些细节:
-
new Function在某些 CSP 策略下被完全禁止(unsafe-eval被禁用时),报错Refused to evaluate a string as JavaScript - Firefox 对
new Function的错误堆栈不显示原始script行号,调试困难 - 若用户脚本含
async/await,new Function返回的是普通函数,需手动包装为async () => { ... }才能 await - 模块环境下(ESM),
globalThis不等于window,且import.meta无法被屏蔽,可能成为信息泄露通道
最麻烦的一点:无论怎么封装,只要允许执行任意 JS 字符串,就无法 100% 防住逻辑层绕过(比如用 Function.constructor('return process')() 尝试提权)。真正的安全边界不在语法层,而在执行环境隔离层。

















