# Prevent sealed APIs from being polluted

**URL:** <https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430>\
**Category:** 💡 Ideas\
**Created:** [September 5, 2025, 6:03pm UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430 "2025-09-05T18:03:39Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [September 5, 2025, 6:03pm UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/1 "2025-09-05T18:03:39Z")

</div>

I’ve been part of a recent “_storm_” in the Node.js world after providing an example of `Promise.prototype.then` pollution that would allow any malicious code to intercept anything out there, especially `crypto` and its `subtle` related operations, which is (imho) a security **no-go**

### Background

Native prototype pollution has been essential to move the Web forward and it’s a more than welcome feature, but in recent years most Web related APIs are `Promise` based and I feel like:

- the `Promise` original API is frozen in time, as in … nobody expects `then` or `catch` to change anytime soon in the next thousand years
- the `crypto` and its `subtle` entries are mostly based on `Promise` API: one leak/evil hook at that prototype, we’re all doomed
- everything that travels asynchronously, including `fetch` operations, can be intercepted, instrumented, and poisoned in a way or another or, worst part, leak in the wild

It’s true that `Promise` is likely my only target for this proposal, but at the same time it’s the most compelling use case for such proposal.

### Proposal

Find out what native API is sealed in time and widely available (close to 100% if not just 100% across vendors/engines) and decide that, if every environment would never ever need a specific patch for such API, as it’s legacy or not inherently leaking data, the descriptor signature out of that API is `writable: false` and `configurable: false`, covering also all accessors in the making (yes, in my report I’ve mentioned even TypedArray buffer access could be poisoned by reaching `Uint8Array. __proto__.prototype` and its `buffer` descriptor).

### Goal

Allow developers to trust that if they `await anything` that `anything` resolved value can’t ever be intercepted by `Promise.prototype.then` pollution, because that pollution in the first place would throw an error out of the box.

This would increase security concerns about most asynchronous APIs already and make the Web, or in general JS as a language, a place to trust more, as opposite of being victim of any _npm module_ out there that got hacked before the next production deployment.

### Risk

There might be modules with honest intents able to provide metrics or whatnot that need to be able to wrap `Promise#then` or any other introspection `Object` or `Reflect` based method around, although that should not be the default allowed to any script, rather an ad-hoc environment (with its flags, like via _playwright_ or _puppeteer_) so that the “_production Web code_” can run in a safer way that doesn’t need ugly/paranoia workaround to maintain.

### Compromise

I think having `crypto` and its `subtle` impossible to hack around would be already a huge step forward, if asking to have _some_ historical prototype sealed by any mean feels like too much, but when it comes to `Promise` I am not asking to seal the whole thing, imagine polyfills impossible to provide a `finally` hook, as example, although I think `Promise.prototype.then` and why not, `catch`, should not be defined just like everything else, as it’s clear the entirety of the Web is moving toward more asynchronous APIs, so that the risk somebody can poison the env at any time and intercept values out of it, or their classes such as `CryptoKey`, would be mitigated out of the box.

### Personal Opinion

I am not sure I know any other scripting language (Ruby, Python, PHP, others) that can poison or pollute, beside being even Server Side targeting PLs, primitives offered by the language itself (`stdClass` in PHP, `dict`, `list` or `tuple` in Python and so on) so that keeping the dynamic nature of JS, as it is, should be a goal, but at the same time “_something gotta be safe out there_” and that something is `Promise.prototype` land together with (at least?) `crypto` and its `subtle` offer.

Thanks in advance for anyone willing to at least open a discussion around this topic.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [September 5, 2025, 6:23pm UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/2 "2025-09-05T18:23:47Z")

</div>

### Previous Work

Forgot to mention that while anyone can sniff `Array` spread or `for of` loops iterations by polluting `Symbol.iterator` on the array prototype, the hidden `Arguments` class does _not_ suffer the same issue, as an iterator method is directly attached, behind the scene, to it, so that it’s not affected by evil code.

That might be it to me, some namespace that attached the native, and secure/safe thing, to its prototype, just like internally browsers won’t leak builtin related operations, and I might be OK with this as resolution at least for `crypto` and `subtle` targets, but of course the `Promise.prototype` would make this amend/change/feature a wonder for the present and the future.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [September 5, 2025, 8:52pm UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/3 "2025-09-05T20:52:53Z")

</div>

Pretty sure preventing promises from being tampered with would break every Angular app out there. Most Angular apps rely on zone.js to try and figure out when it needs to run it's change detection algorithm, and zone.js monkey-patches all sorts of things to help it watch what a component does.

Edit: said things slightly wrong before, fixed

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [September 6, 2025, 1:39pm UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/4 "2025-09-06T13:39:40Z")

</div>

> [@theScottyJam](#):
>
> zone.js

As Angular moves towards a zoneless application development model, Zone.js is no longer accepting new features, including additional patches for native platform APIs. The team will also not be accepting any low priority bug fixes. Any critical bug fixes that relate to Angular's direct use of Zone.js will still be accepted.

While still a supported part of Angular, the Angular team strongly discourages using Zone.js outside of Angular application contexts.

> **[zone.js](https://www.npmjs.com/package/zone.js?activeTab=readme)**
>
> Zones for JavaScript. Latest version: 0.15.1, last published: 3 months ago. Start using zone.js in your project by running \`npm i zone.js\`. There are 13200 other projects in the npm registry using zone.js.

I am not sure because some team decided to use global pathces to work (in 2025 … what a weird choice) and it’s now moving away from that approach (thankfully) the rest of the Web should be kept more problematic than it should be but thanks for pointing that out and happy to read that’s also … **out**

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [September 6, 2025, 3:16pm UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/5 "2025-09-06T15:16:33Z")

</div>

Yes, Angular has recently started providing an alternative option (signals), but it's a really huge lift to migrate an existing codebase to use their new signal API - there's going to be a very significant portion of the web that won't ever make that migration due to their projects having little to maintenance now. Plus, the migration to signals is meant to be optional, Angular is trying to remain backwards compatible (they've already had to deal with the backlash that happened between angular.js to current Angular).

It's one teams decision to make a framework rely on zone.js, but many, many teams decision to then use that framework (including my own workplace actually), and we're going to be stuck with that. My insurance provider’s website, I noticed, also seems to use Angular, and it's probably relying on zone.js (again, only recently did Angular provide an alternative option). I would really hate it if browsers decided to completely break that website until they do a full rewrite to signals. I would also really hate it if we were forced to do a full rewrite to signals as well - we still have angular.js code at our workplace that we haven't rewritten to Angular yet - we're working on it, but these sorts of things take a lot of work - (and so with those projects, we don't even have a signal based approach available to us). And some Angular projects are just unmaintained and will forever be using zone.js.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [September 6, 2025, 3:20pm UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/6 "2025-09-06T15:20:51Z")

</div>

> I am not sure because some team decided to use global pathces to work (in 2025 … what a weird choice) and it’s now moving away from that approach (thankfully) the rest of the Web should be kept more problematic than it should

This actually happens all the time. MooTools (when's the last time you heard of someone using that library?) decided to modify the prototype and add an array.flatten() method, and as a result, when an official flatten method came out,they had to instead call it .flat() to avoid conflict with MooTools. .flatten() had it off easy, the new group by method couldn't even live on the array prototype for similar reasons.

One team's decision on legacy tools effects the future of JavaScript quite a bit.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [September 7, 2025, 5:05am UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/7 "2025-09-07T05:05:26Z")

</div>

Unfortunately making `Promise.prototype.then` frozen at realm creation is likely to be Web incompatible. Besides use cases that actually require it to be mutable (however misguided these may be) it would likely trigger cases of override mistake in code that extend promises without class syntax, or modify promise instances (again however misguided that may be).

Some of us are big fan of [hardening](https://hardenedjs.org/) the intrinsics, and believe we should find a way to make it ergonomic for applications to opt into such a mode (after trusted shims have been applied). It would also be great if engines started better optimizing cases where objects are known to be immutable with primordial behavior. However I strongly believe any freezing of intrinsics should be opt-in.

Back to the promise case, I would love to hear more about the risks associated with hijacked `Promise.prototype.then`. In particular I do not believe alone it allows to interfere with a simple `await`. In that case, native promises are recognized and handled internally through `PerformPromiseThen` which does not trigger `Promise.prototype.then`. The only known reentrancy hazard for native promise instances is through the `.constructor` lookup performed during a `PromiseResolve` “species” check.

Of course `.then` lookups are a major reentrancy hazard when going through the promise resolution steps. We are actually looking at ways to let code (host or user) better protect itself from those hazards, but unfortunately this would have to be an explicit “safe” operation since changing the current `.then` lookup semantics is simply not web compatible.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [September 7, 2025, 5:25am UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/8 "2025-09-07T05:25:58Z")

</div>

For completeness, when doing `return foo` from an async function, unfortunately that path does go through the promise resolve steps that lookup and invoke `.then`

It's actually safer to do `return await foo` as that doesn't trigger the `.then` lookup if foo is a native promise.

However as any `await` it remains vulnerable to a pollution of the `Promise.prototype.constructor`: If you want to disable the “safe” fast path that `await` provides, simply do `Promise.prototype.constructor = Promise.bind(null)` and watch your `Promise.prototype.then` hijack trigger for a simple `await Promise.resolve(42)`.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [September 7, 2025, 7:34pm UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/9 "2025-09-07T19:34:17Z")

</div>

> [@mhofman](#):
>
> For completeness, when doing `return foo` from an async function, unfortunately that path does go through the promise resolve steps that lookup and invoke `.then`

every crypto subtle operation to create keys can be intercepted by `Promise.prototype.then` pollution, as example, that’s the “storm” I’ve mentioned in the OP.

I wrote extremely defensive code that indeed trust internally `PerformPromiseThen` is used but even with my defensive code [things didn’t go nearly as well as expected](https://github.com/nodejs/node/issues/59699) … without my guards, all leaks all over the place for something as delicate as `crypto` operations and its `subtle` were a piece of cake to intercept … meaning you track a `CryptoKey` instance, you have free access to anything that once encrypted can be decrypted.

This is some sort of horror show to me, but the fact any `await` ends up passing through `then` is beyond acceptable for some namespace, namely the crypto one, where I don’t want `zone.js` or any library that needs to patch globals (we’re not talking about polyfills in here, polyfills usually don’t even exist in the first place on those globals) and users can’t naturally write expectations without accidental leaks.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [September 7, 2025, 7:42pm UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/10 "2025-09-07T19:42:00Z")

</div>

P.S. also, `Object.prototype.then` enters the game, if you think native `await` could ever be safe for anything … this is really worrying to me, there’s free ride for any malicious code to make even crypto and/or security or sensitive based API exposed so while `eval` and any framework that in 2025 should be banned from using it can still use the power of eval and hope no _CSP_ rule is around, the `crypto` namespace in particular should be as guarded as `arguments[Symbol.iterator]` is, with their own “impossible to hack” `then` resolvers (still imho).

---

<div class="post-metadata">

**Author:** ![conartist6](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/conartist6/32/515_2.png) [@conartist6](https://es.discourse.group/u/conartist6)\
**Post date:** [September 7, 2025, 11:15pm UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/11 "2025-09-07T23:15:27Z")

</div>

It would be more likely for all the world’s oceans to freeze to Ice-9 than for the committee to decide to tackle an issue this size in a comprehensive way…

---

<div class="post-metadata">

**Author:** ![conartist6](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/conartist6/32/515_2.png) [@conartist6](https://es.discourse.group/u/conartist6)\
**Post date:** [September 8, 2025, 12:03am UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/12 "2025-09-08T00:03:37Z")

</div>

I always thought this is the problem the `import` mechanism was for. Wouldn't it fix the problem if scripts could get crypto function implementations directly from the engine without fear of monkey-patching?

If the analogy were Linux it’s like we have no clear boundary between kernelspace and userspace. With polyfills we often implement engine functionality with scripts. It doesn’t seem to me that this should necessarily be a problem, except that it isn’t the engine orchestrating the scripted extensions: that’s happening in the userspace layer. Because this pattern of userspace scripts like core-js filling engine functionality holds up the web in practice, the blurring of the user and system layers from a security standpoint is standard in the web as it exists today.

The only way I can imagine to fix such a problem is to put the system in charge of defining the system’s APIs (which it can implement in scripts if it wishes) and then allow userspace scripts to have direct/secure conversations with the system, as they would be able to if they could do something like `import { sha256 } from 'js:WebCrypto'`

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [September 8, 2025, 7:21am UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/13 "2025-09-08T07:21:02Z")

</div>

From what I understand of the issue you reported against Node.js, these crypto APIs are implemented in JS, and make some assumptions about the integrity of the JS Realm and its intrinsics.

It is indeed extremely difficult to program defensively against a pollution of the intrinsics, and while Node.js attempts to capture the primordials and use them as much as possible, given how unergonomic it is to program that way, there are bound to be mistakes made.

In the case of promise, the thenable protocol (and species behavior) is pretty pernicious. You’re correct that without changes to the language, it is likely impossible to be defensive against hijacks through pollution of the Promise prototype.

While I still believe that locking down the Realm by explicitly hardening all the intrinsics is the only defensible model, we should try to mitigate some of the cases. There have actually been a few related discussions recently at TC39:

- [Freezing the Array Iterator](https://docs.google.com/presentation/d/1SHqRJDEujG3OVOxs9c3CEnXpHdkzyVrz9qUFyaXrQWA/) discussed on [2027-07-28](https://github.com/tc39/notes/pull/379/files#diff-7cb27fa4d9f73fb698b60cdbeb6c16cb8fcec47a4b6c57b0a2c75b8c92696c16R584)  
I personally would prefer avoiding piece meal freezing of intrinsics at realm creation
- [_How_ to make thenables safer?](https://docs.google.com/presentation/d/1_RCnI7dzyA1COgi_Ib7GkmHioV1nVSEMmZvy0bvJ8Kk/edit?usp=sharing) discussed most recently [2025-07](https://github.com/tc39/notes/pull/379)  
The issue originally started as how to protect hosts (like browsers implementations) from unexpected user code executing through `then` pollutions, mostly vulnerabilities triggered by adding a `Object.prototype.then` getter.

> [@WebReflection](#):
>
> every crypto subtle operation to create keys can be intercepted by `Promise.prototype.then` pollution, as example, that’s the “storm” I’ve mentioned in the OP.

> [@WebReflection](#):
>
> the fact any `await` ends up passing through `then`

To be clear, the trigger in that reproduction does not seem to be in your code `await`-ing a Promise produced by the crypto implementation, but instead is with how the internal `crypto` implementation handles its own promises. That is seemingly confirmed by the crypto implementation in other environments not being susceptible.

I want to re-iterate that `await`-ing a native promise (such as one from calling an async function), is not susceptible to a pollution limited to `Promise.prototype.then`.

That said, such pollution does affect `return` statements inside async functions, and it is still trivial to also pollute `Promise.prototype.constructor` to affect `await` expressions as well, so this is where the spec needs to change to address this.

IMO the following changes are needed:

- Change handling of `return` completions in async functions to follow the `await` semantics of recognizing the native promise and extracting its resolution without going through the `.then` path. This is actually one of the goals of the [Faster Promise Adoption proposal](https://github.com/tc39/proposal-faster-promise-adoption), and is hopefully web compatible.
- Fix the promise "species" logic in `PromiseResolve` to avoid triggering any user code, or being susceptible to `.constructor` pollution. There is an [open issue](https://github.com/tc39/ecma262/issues/3662) already to do this, related to the "safer thenable" discussions.
- Some way of making objects unexpectedly thenable, in particular through `Object.prototype.then` pollution. I actually am really warming up to [the idea](https://github.com/tc39/proposal-thenable-curtailment?tab=readme-ov-file#how-do-i-propose-we-fix-this) of making `Object.prototype` more exotic so it cannot grow a `then` property.

Together these ensure that no unexpected code would execute when `await`-ing or `return`-ing a primitive value, non-thenable object or native base promise. User code would still execute for derived promises, and thenable objects (with a `.then` property anywhere on the prototype chain, except for the root `Object.prototype`). In particular, handling through syntax the promise created by an async function or other built-ins would not be susceptible to pollution of the intrinsics.

I'm also really hopeful we can get explicit "Safe Promise Capabilities", which would guarantee no synchronous reentrancy when handling possibly tainted objects. I don't know how much we can change existing Promise functions to recognize native promise instances instead of relying on the "thenable" protocol (the goal of the faster promise adoption proposal), but if not we could likely make that a part of the "Safe Promise Capabilities" approach.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [September 8, 2025, 7:34am UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/14 "2025-09-08T07:34:43Z")

</div>

Thanks a lot for all details, anything better than the current state would be a huge step forward to me so I’d welcome every single proposal/improvement you mentioned and I am happy this discussion wasn’t “just closed” as it happened in the mentioned thread.

Looking forward to read about / see progress around this topic 👋

---

<div class="post-metadata">

**Author:** ![Jingcong](https://avatars.discourse-cdn.com/v4/letter/j/8797f3/32.png) [@Jingcong](https://es.discourse.group/u/Jingcong)\
**Post date:** [April 16, 2026, 10:37am UTC](https://es.discourse.group/t/prevent-sealed-apis-from-being-polluted/2430/15 "2026-04-16T10:37:21Z")

</div>

Generally true because websites and the internet rely too much on promise based web solutions. For example, the https fetch/post api called "axios," which already counts as a dependency even states in its description: "A promise-based..." which shows even extensions rely on other js features. This is very dangerous if a hacker **does** find how to abuse the C code used inside the compiler.
