The Standards and Web Platform team at Salesforce is championing the creation new features running natively in the web browsers. This post will describe how the new EcmaScript’s ShadowRealm API will improve Salesforce’s security and integrity mechanisms, and how it can be used as a building block for virtualization frameworks, such as the Lightning Web Security.
As with any platform, the web expects its many applications to be composed in many different ways, which makes plenty of room for creative experiences from multiple sources. When the Salesforce Platform embraces the web, it takes the idea of collective composition to its core. This happens when our customers shape the way they use Salesforce with their most creative customizations and share their experience with other customers. Those compositions end up in programs with multiple sources, whether from different teams or vendors, and with a multitude of environment requirements — all of them simultaneously connected to the Salesforce Platform.
In web applications, all things are shared within a single root global environment, which is represented by the main Window object. This is where the core of the Salesforce application runs in the browser and connects to many of its components, including those tailored by customers. It’s fundamental to preserve the integrity of this execution as a whole as it is to maintain security within each piece used for a customer composition.
Applications can set footprints over the global scope and at the available built-in objects. These modifications may vary from adding names to the global (e.g.,
$ for jQuery) to patching common methods (e.g., adding a custom behavior to
Array.prototype.sort). These modifications may impact the integrity of an application that is composed by many components and/or libraries.
What is ShadowRealm?
However, ShadowRealm instances can wrap and “share” function values. This enables robust communication channels back and forth between shadow realms.
Wrapped functions can also send other functions to be wrapped in the receiving realm.
This communication is synchronous and can be used to get immediate status of elements in the page or the accurate state of an application.
As the global object is not shared, there are fewer issues of multiple components setting custom, unexpected values on standard built-in objects:
Using ShadowRealm with no string evaluation
ShadowRealm.prototype.evaluate operates similarly to an indirect
eval(), with the code running in the respective ShadowRealm instance. This means that the code evaluation is subject to the existing Content Security Policy (CSP) just like the main page. For example,
unsafe-eval prevents usage of
If the code string evaluation is not possible, there is another way to inject code into a ShadowRealm instance.
ShadowRealm.prototype.importValue allows a dynamic module import — as in the
import() expression — to load a module and capture an export value, including wrapped functions.
The modules are evaluated per realm, meaning modifications won’t share values or observe globals set from different realms. For example, the module from
./foo.js has the following code:
In this case,
shadowSum is a function that wraps
sum loaded inside the shadow realm and — when called — it only affects the
globalThis.total from within the respective shadowRealm. In the same way, if the module
./foo.js is loaded in another realm, such as the page realm, the
sum function will observe the respective module it was loaded at.
importValue is intentionally designed to require a value to be imported from the given module as a starting point to set a communication channel with the ShadowRealm instance. It does not return any module namespace object, such as the regular
import() expression, as the objects are currently not allowed to cross the shadow realm boundary.
evaluate method is subject to CSP restrictions, such as
importValue method is subject to CSP restrictions set to the page, such as
default-src. If this directive is present, the API won’t be able to load code this way.
Low-level code and virtualization
The ShadowRealm API contains only two methods and is designed for use in low-level code. The ShadowRealm can often be used as the base for virtualization or code sandboxing systems. The ShadowRealm API is not designed to be used directly by a final user; it requires a framework layer on top, like Lightning Web Security (LWS). LWS uses a membranes framework to communicate the execution of values and states across the ShadowRealms and the main application Window.
The exposed built-in API surface is also likely to be smaller compared to the top-level Window object seen in the main page or those from iframes. Importantly, the global object in a ShadowRealm instance is an ordinary object with all of its properties configurable, which roughly means any global property can be deleted and directly contrasts with the infamous unforgeable global properties from iframes, such as
window.location. These properties are always present in the globals of iframes and cannot be removed. ShadowRealm prevents this, not only by not adding any unforgeable value, but by also setting rules requiring any of the values provided by the host to be effectively deletable.
The new API sets boundaries for code execution that can take advantage of a clean canvas that is not only modules-ready, but can be properly used in the web for virtualizing DOM manipulation that runs from low to no footprint. That differentiates from code that would run without integrity concerns in a clean page. The ShadowRealm API can be a useful and powerful base for membrane frameworks and other encapsulation pieces available in the web.
Roadmap for ShadowRealms
There is more to improve for ShadowRealm, as plenty of this discussion leans towards lowering the bar to attach a membrane framework, or, perhaps, standardizing a membrane framework directly. Part of this discussion weighs the idea of bringing serialization over objects in order to offer a smart solution that is already present in the web today, such as in web workers. Other discussions include creating mechanisms to wrap and unpack promises, iterators, and async iterators across shadow realm boundaries.
About the authors
Leo Balter (he/him) is a Senior Product Manager driving the Standards and Web Platform forward at Salesforce. As a TC39 delegate, Leo is currently championing the ShadowRealm API to ensure that it becomes part of ECMAScript. While not working, Leo loves to play Jazz standards on one of his many guitars.