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
Promiseoriginal API is frozen in time, as in … nobody expectsthenorcatchto change anytime soon in the next thousand years - the
cryptoand itssubtleentries are mostly based onPromiseAPI: one leak/evil hook at that prototype, we’re all doomed - everything that travels asynchronously, including
fetchoperations, 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.