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/proposal-thenable-curtailment.md.
  • 简体中文
  • Curtailing the power of "Thenables" S2

    中文标题:限制“Thenables”的能力(SafeResolve)

    提案概览
    提案速览

    该提案针对 thenable 的 then 查找沿完整原型链进行所引发的风险与复杂性,这种查找可能在 promise 解析期间意外执行用户代码,造成安全漏洞。它引入了一种“SafeResolve”promise 解析操作:先检查解析 promise 是否可能运行用户代码;若不可能,则直接调用 promise capability 的 \[\[Resolve\]\];若可能,则改为入队一个任务,以锁定语义解析 promise,从而忽略后续解析。该操作预期用于内部场景(例如 WebIDL 的 promise 解析),向用户代码暴露它不是本提案的目标。

    Note

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

    限制“Thenables”的能力(SafeResolve)

    Champion(提案负责人):Matthew Gaudet(Mozilla)

    阶段:2.7(截至 2026 年 7 月全体会议)

    规范草案文本https://tc39.es/proposal-thenable-curtailment/

    引言与问题:

    引用 MDN:

    JavaScript 生态系统在 Promise 成为语言的一部分之前就已经有了多种 Promise 实现。尽管这些实现内部表示不同,但所有类 Promise 对象至少都实现了 Thenable 接口。Thenable 实现了 .then() 方法,该方法接收两个回调:一个用于 promise 兑现时,一个用于 promise 拒绝时。Promise 本身也是 thenable。

    为了与现有的 Promise 实现互操作,语言允许使用 thenable 代替 promise。例如,Promise.resolve 不仅会解析 promise,还会追踪 thenable。

    我们要解决的问题是:then 的查找会沿着整个原型链进行,包括内置原型和 Object.prototype。在处理那些“thenable 派发”并不预期的类型时,这尤其危险。

    为什么这是一个问题?

    最具体的后果是安全漏洞。我们必须始终对这一兼容性支持特性保持高度警惕,在所有标准工作和整个 Web 平台中都是如此。否则可能导致被利用:

    这一特定问题被指为导致安全漏洞的原因是:它为用户代码执行增加了许多原本不存在的路径,而且并不总是很明显地存在这种可能性。

    尤其危险的是,规范作者将已知类型的新生对象视为已知量,却对它们调用 Promise.resolve。在这种情况下,当提供给它们一个 JS 包装对象时,该 JS 包装对象的原型通常是 Object,从而使它们易受 thenable 攻击。

    除了安全之外,这也只是引入了复杂性。WPT 中有一些测试用例完全是为了搞清有人破坏 then的预期行为。

    我提议如何修复?

    我想提议添加一个“SafeResolve”解析操作,它在检查是否可能运行用户代码之后解析 promise。如果我们无法运行任何用户代码,则直接尾调用 promise capability 的 [[Resolve]] 操作。如果可能运行用户代码,我们则改为将一个新任务入队,该任务负责解析 promise,同时以与常规 resolve 函数相同的方式锁定该 promise,使得任何未来的解析都会被忽略(非常感谢 Mark Miller 在 TG3 审查讨论中发现了这一要求)。

    下一步是决定如何使用它。Mozilla DOM 方面有兴趣探索使用它来替代 WebIDL 中解析 promise 的步骤,并更广泛地支持 Mozilla DOM 中所有 promise 解析代码。这将使 promise 解析成为永不运行脚本的操作,从而使 C++ 代码更安全,简化实现代码时所需的推理。

    关于 WebIDL 使用的规范讨论正在进行中,见 whatwg/webidl #1584

    将此功能暴露给用户代码不是本提案的目标,但最终可以作为后续提案完成。

    这是万无一失的修复吗?

    不是。其影响当然完全取决于采用范围。

    在之前描述的安全漏洞中,此缓解措施能修复:

    然而它不会修复:

    • CVE-2024-43357 规范漏洞。那需要进行一项规范性修改,以开始在规范_内部_消费这一能力。

    兼容性

    这可能会改变涉及“thenables”时微任务的解析顺序。希望大多数处理 promise 的代码对执行顺序已经相对健壮。然而,这确实有可能导致 Web 兼容性问题。

    实验:WebIDL

    问:我们能否用 SafePromiseResolve 替换 WebIDL 中的 promise 解析步骤

    实验:使用 SafePromiseResolve 替换 Firefox DOM 的 promise 解析步骤来运行 WPT1

    结果:

    绝大多数测试(如预期)通过。

    超时失败:
    1. https://searchfox.org/firefox-main/source/testing/web-platform/tests/css/css-overflow/scroll-marker-in-display-none-column-crash.html —— 我还没完全搞清楚这个问题。
    2. /custom-elements/when-defined-reentry-crash.html -- 这个测试在 Object.prototype 上使用 then 以达到不良目的。从某种意义上说,这正是我们要解决的问题。https://issues.chromium.org/issues/40061097
    意外的通过:
    1. /fetch/api/response/response-body-read-task-handling.html —— 这个测试使用 then 来窥探执行顺序。该测试不再测试它原本以为在测试的内容了;不过,该测试 -also- 是为了解决这类 thenable 问题而创建的。
    测试失败
    1. /streams/readable-byte-streams/patched-global.any.js —— 明确使用 then 来窥探我们可能更希望不可观察的执行状态。
    2. /document-picture-in-picture/returns-window-with-document.https.html | requestWindow timing - assert\_equals: Got the expected order of actions expected "requestWindow,microtask,enter" but got "microtask,requestWindow,enter" -- 任务时机改变,因为它用一个 window(WindowProxy)对象解析 promise,导致多了一个 tick。
    3. /web-animations/interfaces/Animation/cancel.html;使用 thenable 观察事件时序。

    先前工作与相关工作

    • Symbol.thenable “已撤回;在模块命名空间对象上更改 thenability 不兼容 Web,并且允许非 Promise 使用“then”不值得减慢所有 Promise 操作”
    • Proposal Stabilize 试图为不变式提供可推广的机制——这或许也可以成为我们能够提供给用户代码的一种不变式。

    提案历史

    Footnotes

    1. 这比严格意义上的 WebIDL 略微宽泛,因为我认为 Mozilla 的 dom::Promise 存在非 IDL 使用。