For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/pending/proposal-dynamic-code-brand-checks.md.
  • 简体中文
  • Dynamic Code Brand Checks S3

    中文标题:动态代码品牌检查

    提案概览
    提案速览

    该提案解决了当前动态代码评估(eval 和 new Function)防护的局限性,允许宿主对表示代码的对象进行品牌检查,并在决定是否允许编译时向宿主提供更多上下文(类型信息和完整的组装代码字符串)。它引入了 HostGetCodeForEval 抽象,并修改了 HostEnsureCanCompileStrings 宿主回调的签名。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    TC39 提案:动态代码品牌检查

    目录

    状态

    冠军:koto
    作者:mikesamuel, koto
    阶段:3
    规范:ecmarkup 输出源文件

    TL;DR

    允许宿主创建_类似代码的_对象,并将 HostEnsureCanCompileStrings( calleeRealm, parameterStrings, bodyString, direct ) 改为 HostEnsureCanCompileStrings( calleeRealm, parameterStrings, bodyString, codeString, compilationType, parameterArgs, bodyArg )

    动机

    eval 是邪恶的

    eval 函数是 JavaScript 中最被滥用的特性。避免使用它。

    -- Douglas Crockford, "JavaScript: The Good Parts"

    eval 及其朋友 new Function 是有问题的,因为攻击者常常可以将其反过来对付应用程序。

    大多数代码避免使用 eval,但 JS 程序不再像 Crock 写那句话时那样小巧和自包含。

    如果一个模块使用了 eval,即使只是像 Function('return this')() 这样获取全局对象的句柄,那么 eval 就必须工作。

    这阻止了诸如以下安全措施的使用:

    这些措施会全局关闭 eval

    随着 JavaScript 程序变得越来越大,没有任何部分需要 evalFunction() 才能运行的可能性就越小。


    在 JavaScript 中,代码审查者很难确定代码从不使用这些运算符。例如,当 x 是构造函数时,下面的代码可以执行。

    ({}[x][x](y)());
    
    // ({})[x]       === Object
    // ({})[x][x]    === Function
    // ({})[x][x](y)  ~  Function(y)

    因此,代码审查和开发者培训不太可能防止这些运算符的滥用。


    这旨在通过向宿主环境提供更多上下文来解决这个问题,以便它们可以做出更细粒度的信任决策。由于 JavaScript 程序中的字符串通常来自不受信任的来源(Web 平台中的基于 DOM 的 XSS),信任决策可能基于对表示应用程序作者信任的代码的_对象_进行品牌检查。

    可信类型

    Trusted Types 提案(面向 TC39 的说明)试图通过要求代码部分具有指示其已被明确信任的运行时类型来保护诸如动态代码评估之类的危险操作。

    具体来说,当 Trusted Types 强制执行开启时,它希望确保传递给 eval()new Function() 的参数是(或可转换为)TrustedScript - 一个包装了存储在内部不可变槽中的字符串的对象。这增强了 Web 平台已有的通过 CSP 进行的动态代码评估防护(Trusted Types 也与其他 CSP 机制集成)。

    本提案旨在解决以下问题:

    问题 1:%eval% 不接受对象代替字符串作为代码

    目前,typeof x === 'string' || eval(x) === x。当参数不是字符串时,Eval 会提前退出。

    例如:

    console.log(eval(123) === 123);
    
    const array = ["alert(1)"];
    console.log(eval(array) === array); // 不会弹出警报
    
    const touchy = {
      toString() {
        throw new Error();
      },
    };
    console.log(eval(touchy) === touchy); // 不会抛出异常。

    这遵循了 PerformEval 定义的第 2 步:

    运行时语义:PerformEval ( x, evalRealm, strictCaller, direct )

    抽象操作 PerformEval 带有参数 *x*, *evalRealm*, *strictCaller* 和 *direct* 执行以下步骤:

    1. 断言:如果 directfalse,则 strictCaller 也为 false
    2. 如果 Type(x) 不是 String,返回 x

    向后兼容性约束

    为了避免破坏 Web,当 eval(x) 的 x 是现有应用程序可能创建的非字符串值时,eval(x) 不应做任何不同的事情。

    解决方案

    定义一个规范抽象 IsCodeLike,允许某些对象值通过,但不会改变现有程序的语义。

    • 定义 HostGetCodeForEval(*x*),一个规范抽象,对于应该求值的对象返回代码字符串。
    • 调整 PerformEval,它被直接和间接的 eval 调用,以使用 HostGetCodeForEval(*x*) 确保类似代码的对象不会导致提前退出。
    优点
    • HostGetCodeForEval 对值没有可观察的副作用。当程序不产生类似代码的值时,eval 向后兼容。
    • 用户代码只能通过宿主定义的 API 创建类似代码的值。
    缺点
    • 无法在 test262 中测试(类似代码的对象的创建由宿主定义)。
    • 类似代码的值不可代理(proxyable)。

    问题 2:宿主回调不接收类型信息

    目前,决定是否允许编译字符串的可用信息是一个 realm、一个参数字符串列表、一个正文字符串和一个布尔值。

    HostEnsureCanCompileStrings( _calleeRealm, parameterStrings, bodyString, direct )

    HostEnsureCanCompileStrings 是一个实现定义的抽象 操作,允许宿主环境阻止某些 ECMAScript 函数,这些函数允许开发者将字符串编译成 ECMAScript 代码。

    为了让宿主能够有效地防护动态代码评估,需要额外的类型信息。

    解决方案

    本提案旨在向 HostEnsureCanCompileStrings 提供额外的上下文,并重新排序 CreateDynamicFunction 中的步骤,以便 HostEnsureCanCompileStrings 接收运行时类型信息 - 即 eval 参数或所有 Function 构造函数参数。

    缺点
    • 需要对宿主回调进行更改(另见下文):
    • 复杂的宿主接口;需要将对象传递给宿主。

    问题 3:宿主回调不接收要检查的完整代码

    HostEnsureCanCompileStrings 被调用时带有源代码的参数,但它们不是统一的字符串。

    解决方案

    本提案更新宿主回调以包含要执行的完整代码字符串,并将 CreateDynamicFunction 中的回调移动到函数体组装之后。

    测试

    相关测试位于

    但测试这些需要影响宿主回调的行为,因此可能需要指定为 web-platform-tests