Champion: Matthew Gaudet (Mozilla)
Stage: 2 (As of March 2026 Plenary)
Draft Spec Text: https://tc39.es/proposal-thenable-curtailment/
Quoting MDN:
The JavaScript ecosystem had made multiple Promise implementations long before it became part of the language. Despite being represented differently internally, at the minimum, all Promise-like objects implement the Thenable interface. A thenable implements the
.then()method, which is called with two callbacks: one for when the promise is fulfilled, one for when it's rejected. Promises are thenables as well.To interoperate with the existing Promise implementations, the language allows using thenables in place of promises. For example, Promise.resolve will not only resolve promises, but also trace thenables.
The problem we would like to address is that then lookup follows the whole
prototype chain. Including builtin prototypes and Object.prototype. This is
particularly dangerous when working with types where 'thenable' dispatch was
unexpected.
The most concrete one is security vulnerabilities. We must be ever-vigilant about this compatibility supporting feature in all standard work, and throughout the web-platform. Failure to do so has the consequence of possible exploitation:
- CVE-2024-43357 on the specification.
- Out of bound access in ReadableStream::Close
- CVE-2021-21206: Chrome Use-After-Free in Animations
- CVE-2024-9086
- Others not disclosed here.
The reason this particular issue is fingered for causing security vulnerabilities is that it adds many paths for user code execution which otherwise don't exist, and is not always obviously a possibility.
Of particular danger is where specification authors think of newborn objects of known
types as known quantities, only to call Promise.resolve on them. At this point
when they are provided a JS wrapper the JS wrapper typically has Object as their
prototype, making them vulnerable to thenables.
Beyond security, this also just injects complexity. There are test cases in WPT that
exist purely to work out the expected behaviour for someone breaking then
I'd like to propose we add a "SafeResolve" resolve operation, which resolves a promise after
checking for the possibility of running user code. If we cannot run any user code, we simply
tail-call into the promise capability's [[Resolve]] operation. If we could run user-code,
we instead enqueue a new job whose responsibility is to resolve the promise while also latching
the promise in the same way the regular resolve functions do, such that any future resolutions
are ignored (Thank you very much to Mark Miller for catching this requirement in TG3 review discussion).
The next step is to decide how to consume this. There is interest from the Mozilla DOM to explore using this to replace the steps for resolving a promise in WebIDL and more generally powering all the promise resolution code in Mozilla's DOM. This would help make C++ code safer by making promise resolution into an operation that never runs script, which simplifies the reasoning required when implementing code.
Specification discussion about WebIDL consumption is happening at whatwg/webidl #1584
Exposing this to user-code is a non-goal of this specific proposal, but can be done as a followup proposal eventually.
No. The impact of this will of course depend entirely on the scope of adoption.
Of the previously described security bugs this mitigation would fix
- Out of bound access in ReadableStream::Close
- Some of the undisclosed bugs.
- CVE-2024-9086, as in this bug it would be sufficient to add the "then" property to an instance already escaped to script
- I suspect the Chrome Animation UAF but am not 100% sure
It would not however fix
- CVE-2024-43357 on the specification. That would require a normative change to start consuming this capability within the specification.
This could change the order in which microtasks get resolved when 'thenables' are involved. The hope is that the majority of code dealing with promises is already relatively robust to execution order. However, it is certainly plausible this could cause a web compatibility problem.
Q: Can we use a SafePromiseResolve to replace the promise resolution steps in WebIDL?
Experiment: Run WPT with the Firefox DOM Promise resolve steps replaced with SafePromiseResolve1.
The vast majority of tests (as expected) pass.
- https://searchfox.org/firefox-main/source/testing/web-platform/tests/css/css-overflow/scroll-marker-in-display-none-column-crash.html -- I didn't quite figure this one out.
- /custom-elements/when-defined-reentry-crash.html -- this one is using a
thenon Object.prototype for nefarious aims. In a sense this is exactly the kind of issue we’re trying to address. https://issues.chromium.org/issues/40061097
- /fetch/api/response/response-body-read-task-handling.html - This test is using
thento get insight into execution order. The test no longer tests what it thinks it is testing anymore; however the test -also- was created to address this kind of thenable issue.
- /streams/readable-byte-streams/patched-global.any.js -- Explicitly using
thento peek into execution state we’d probably prefer to not be observable. /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"-- The job timing changes because it’s resolving a promise with awindow(WindowProxy) object, which causes an extra tick.- /web-animations/interfaces/Animation/cancel.html; observing event timing with thenable.
Symbol.thenable"Withdrawn; changing thenability on Module Namespace objects is not web compatible, and allowing non-Promise use of "then" is not worth slowing down all Promise operations"- Proposal Stabilize is trying to provide generalizable machineries for invariants -- this could be more of an invariant we could provide to user code as well.
- Presented at February 2025 Plenary -- Achieved Stage 1. (Notes)
- Presented at July 2025 Plenary. (Notes & Notes from Continuation)
- Presented at March 2026 Plenary
- Presented at May 2026 Plenary (Notes still to be posted)
Footnotes
-
This is slightly more broad than strictly doing WebIDL because I think there’s non IDL use of dom::Promise. ↩