ESM模块无法隔离eval()作用域,因其动态执行始终穿透模块边界访问调用点作用域链;静态词法隔离仅适用于let/const/function等声明,对eval无效。

ESM 模块本身不能隔离 eval() 的作用域,它对 eval 的变量访问控制完全无效。模块的顶级作用域隔离只适用于 静态声明(如 let/const/function),而 eval 是运行时动态求值机制,它始终穿透模块边界,直接访问调用点所在的作用域链。
为什么 ESM 的模块封装对 eval 没用
ESM 的“作用域隔离”是指:模块内顶层声明的变量默认不挂载到全局、不会被其他模块无意读写——这是通过语言级的 词法环境静态划分 实现的。但 eval 不受此约束:
- eval 总是在调用它那一行代码所处的作用域中执行,不是在模块顶层,也不是在函数闭包定义处;
- 即使 eval 写在一个 ESM 文件的最外层,它仍能访问该模块中所有已声明的 let/const 变量;
- 如果 eval 出现在某个函数内部,它还能访问该函数的参数和局部变量,不管这个函数是否 export。
真实泄漏场景示例
假设你有这样一个 ESM 文件 math.js:
let secretToken = 'abc123';
const config = { api: 'https://api.example.com' };
export function calculate(expr) {
return eval(expr); // 危险!
}
调用方哪怕只是 import 这个模块,一旦传入恶意字符串,就能直接读取或篡改:
-
calculate('secretToken')→ 返回'abc123'; -
calculate('config.api = "https://hacker.com"; config')→ 修改了模块私有配置; -
calculate('fetch("/admin/delete", {method:"POST"})')→ 发起越权请求。
new Function 是更可控的起点,但需手动加固
相比 eval,new Function 至少不继承调用者局部变量,它的作用域链只链接到全局或模块顶层。你可以利用这点构建轻量沙箱:
- 把需要暴露的变量显式作为参数传入:
new Function('a', 'b', 'return a + b'); - 避免将
window、document、localStorage等敏感对象注入执行上下文; - 强制启用严格模式(
new Function默认开启),禁用with和隐式全局赋值; - 配合 AST 解析器做语法白名单校验,比如只允许数字、运算符、括号和预设变量名。
根本建议:优先重构,而非加固 eval
绝大多数使用 eval 的场景其实有更安全、更清晰的替代方案:
- 动态计算表达式 → 用
Function构造 + 参数绑定,或引入 mathjs、expr-eval 等专用库; - 配置驱动逻辑 → 改为 JSON Schema + 条件判断,或用策略模式预定义函数映射;
- 插件化扩展 → 基于 ESM 动态 import 加载可信模块,而非拼接字符串执行;
- 调试/开发工具 → 仅限本地非生产环境,且明确限制可访问变量范围(如只传入 console、JSON)。

















