Isolated Web Apps
The web is a truly unique application platform. Apps built on it are instantly accessible on any operating system without any code changes or compiling needed. Whenever a user comes to your app, they always have the most up-to-date version. They’re installable and can work offline, are highly capable, and super shareable with just a link. Build a web application, and it just works, everywhere.
Because the web aims to be safe and secure by default, its security model needs to be very conservative. Any new capabilities added need to be safe for a casual user to accidentally come across through a URL. We call this security model the drive by web. While this is great for many applications, and can be made more secure using Content Security Policies and cross-origin isolation, it doesn’t work for every use-case. A number of really important, and very powerful APIs, like Direct Sockets and Controlled Frame, that developers need can’t be made safe enough for the drive by web.
For these applications, they don’t currently have an option for building on the web. For others, the web’s security model may not be conservative enough; they may not share the assumption that the server is trustworthy, and instead prefer discretely versioned and signed stand-alone applications. A new, high-trust security model is needed. Isolated Web Apps (IWAs) provide an isolated, bundled, versioned, signed, and trusted application model built on top of the existing web platform to enable these developers.
A spectrum of trust on the web
You can think of security and capabilities on the web in terms of a spectrum.
The drive-by web, on the left, has the lowest trust security model because it needs to be most accessible, and therefore has the least access to a user’s system. Browser-installed web apps, in the middle get a little more trust, and can integrate a little deeper into a user’s system. Users can generally switch between drive-by web versions of apps and browser-installed versions without problem.
Then there’s high-trust, Isolated Web Applications.
They act and feel more like native apps and can get access to deep system integrations and powerful capabilities. Users can’t jump between them and the drive-by-web. If you need this level of security, or these capabilities, there’s no going back.
When trying to decide where on this spectrum you should aim for, default to the lowest-trust security model that you can, like a Progressive Web App. This will give you the greatest reach, require you to manage the least amount of security concerns yourself, and will be the most flexible for your developers and users.
Secure by design
Isolated Web Apps provide a high-trusted security model for web applications. To enable that, though, some of the assumptions that the drive by web makes about trust need to be rethought. Core web building blocks, like servers and DNS, can no longer be explicitly trusted. Attack vectors that may seem more relevant for native apps suddenly become important. So, to gain access to the new high-trust security model provided by IWAs, web apps need to be packaged, isolated, and locked down.
Packaged
Pages and assets for Isolated Web Apps can’t be served from live servers or fetched over the network like normal web applications. Instead, to gain access to the new high-trust security model, web apps need to package all of the resources they need to run into a Signed WebBundle. Signed web bundles take all of the resources required to run a site and package them together into a .swbn
file, concatenating them with an integrity block. This allows the web app to be securely downloaded in its entirety, and even shared or installed while offline.
This, however, poses a problem for verifying the authenticity of a site’s code: TLS keys require an internet connection to work. Instead of TLS keys, IWAs are signed with a key that can be kept securely offline. The good news is that, if you can gather all of your production files into a folder, you can turn it into an IWA without much modification.
Generating signing keys
Signing keys are Ed25519 or ECDSA P-256 key pairs, with the private key being used to sign the bundle and the public key used to verify the bundle. You can use OpenSSL to generate and encrypt an Ed25519 or ECDSA P-256 key:
Signing keys have a secondary purpose, too. Because a domain may be vulnerable to loss of control like a server, it can’t be used to identify the installed IWA. Instead, an IWA is identified by the bundle’s public key, which is part of its signature and tied to the private key, instead. This is a significant change to how the drive-by web works, so instead of using HTTPS, IWAs use a new scheme, too: isolated-app://
.
Bundle your app
With your signing key available, it’s time to bundle your web app. To do so, you can use the official NodeJS packages to bundle then sign your IWAs (Go command-line tools are also available). First, use the, wbn package, pointing to the folder containing all of your IWA’s production files (here dist) to wrap them up into an unsigned bundle:
This will generate an unsigned web bundle of that directory to out.wbn.
Once generated, use the encrypted Ed25519 or ECDSA P-256 key you previously created to sign it using wbn-sign:
This will generate a signed web bundle from the unsigned web bundle called signed.swbn
. Once signed, the tool will also output the Web Bundle ID and its Isolated Web App origin. The Isolated Web App origin is how your IWA is identified in the browser.
If you are using Webpack, Rollup, or a tool that supports their plugins (like Vite), you can use one of the bundler plugins (Webpack, Rollup) that wraps these packages instead of calling them directly. Doing so will generate a signed bundle as the output of your build.
Test your app
You can test your IWA in one of two ways: either by running your development server through Chrome’s built-in IWA developer proxy, or by installing your bundled IWA. To do so, you’ll need to be on Chrome or ChromeOS 120 or later, enable the IWA flags, and install your app through Chrome’s Web App Internals:
- Enable the
chrome://flags/#enable-isolated-web-app-dev-mode
flag - Test your IWA by going to Chrome's Web App Internals page at
chrome://web-app-internals
Once on the Web App Internals page, you have two choices: Install IWA via Dev Mode Proxy
or Install IWA from Signed Web Bundle
.
If you install through a Dev Mode Proxy, you can install any URL, including sites running from a local development server, as an IWA, without bundling them, presuming they meet the other IWA installation requirements. Once installed, an IWA for that URL will be added to your system with the correct security policies and restrictions in place and access to IWA-only APIs. It will be assigned a random identifier. Chrome Dev Tools is also available in this mode to help you debug your application. If you install from a Signed Web Bundle, you’ll upload your signed, bundled IWA and it will install as if it had been downloaded by an end-user.
On the Web App Internals page, you can also force update checks for any applications installed through Dev Mode Proxy or from a Signed Web Bundle to test the update process, too.
Isolated
Trust is key to Isolated Web Apps. This starts with how they run. Users have different mental models for what an app can, and should be able to, do depending on whether it’s running in a browser or in a stand-alone window, generally believing that stand-alone apps have more access and are more powerful. Because IWAs can gain access to high-trust APIs, they’re required to run in a stand-alone window to align with this mental model. This visually separates them from the browser. But it goes further than visual separation.
Isolated Web Apps run on a separate protocol than in-browser websites (isolated-app
vs http
or https
). This means each IWA is entirely separated from websites running in-browser, even if they’re built by the same company, thanks to the same-origin policy. IWA storage is also separated from each other. This together ensures that cross-origin content is guaranteed not to leak between different IWAs or between IWAs and a user’s normal browsing context.
But neither isolation nor bundling and signing a site’s code are useful for establishing trust if an IWA can download and run arbitrary code after installation. To ensure this while still allowing IWAs to connect to other sites for content, IWAs enforce a rigorous set of Content Security Policies:
- Only allows JavaScript from the bundle; however, it does allow Wasm to be run no matter its source. (
script-src
) - Allows JavaScript to fetch from secure, non-localhost cross-origin domains, connect WebSocket and WebTransport endpoints, and blob and data URLs (
connect-src
) - Guards against DOM cross-site script injection (XSS) attacks by regulating how DOM manipulation functions can be used (
require-trusted-types-for
) - Allows frames, images, audio, and video from any HTTPS domain (
frame-src
,img-src
,media-src
) - Allows fonts from the bundle and blobs (
font-src
) - Allow inline CSS or CSS from the bundle (
style-src
) - <object>, <embed>, and <base> elements cannot be used (
object-src
andbase-uri
) - Only allows resources from the bundle for any other CSP-covered requests (
default-src
)
These CSPs aren’t enough to fully protect against potentially malicious third-party code. IWAs are also cross-origin isolated, setting headers to reduce the ability of third-party resources to affect them:
- Only allow resources from the bundle or cross-origin resources explicitly marked as supporting CORS with either a cross-origin resource policy (CORP) header set or the
crossorigin
attribute. (Cross-Origin-Embedder-Policy
) - Disallow no-CORS cross-origin requests (
Cross-Origin-Resource-Policy
) - Process-isolate the browsing context from cross-origin documents, preventing window.opener references and global object access (
Cross-Origin-Opener-Policy
) - Prevent the site from being embedded within a frame or iframe (CSP,
frame-ancestors
)
Even with these restrictions in place, there’s one more potential attack that IWAs guard against: sequence breaking attacks. A sequence breaking attack happens when malicious third-party content attempts to create a confusing and potentially exploitable user experience by navigating to a page in an unexpected way, like navigating directly to an internal settings page. IWAs prevent this by disallowing arbitrary deep-linking from external sites, only allowing applications to be opened by navigating to well-defined entry points, like a start_url
, a protocol handler, a share target, or through a launch handler.
Locked down
Packaging and isolation provide a set of guarantees around what is allowed to run and where it came from, but the dynamic nature of permissions on the web means they alone can’t ensure that a web application is only using the capabilities it needs. Because different capabilities have different security considerations, a user or administrator will want to audit what permissions an IWA may use, just like they can do with other native apps like Android and iOS before they install or update an app.
To facilitate this, Isolated Web Apps block all permission requests by default. Developers can then opt-in to the permission they need by adding a permissions_policy
field to their Web App Manifest. This field contains key/value pairs of permission policy directives and permission policy allowlists for each permission the IWA, or any child frame like a Controlled Frame or an iframe, may request. Adding a permission here does not automatically grant it; instead it makes it available to be requested when a request for that capability is made.
Consider you’re building a fleet tracking IWA as an example. You may need your IWA to be able to request the user’s location, and for an embedded map to request location, too. You may also want any embedded site to be able to go fullscreen to give an immersive view for the user. To do so, you’d set up the following permission policy in your Web App Manifest:
Because WebBundles can specify Permissions Policy headers, too, only permissions declared in both will be allowed, and then only origins in the allowlists that are in both will be allowed.
Named and versioned
Normal web apps rely on their domain name to identify themselves to users and can be updated by changing code that’s served at that domain. But because of the security constraints around Isolated Web Apps, identity and updates need to be handled differently. Much like Progressive Web Apps, Isolated Web Apps need a Web App Manifest file to identify them to your users.
Web app manifest
Isolated Web Apps share the same key manifest properties for their Web App Manifest as PWAs, with some slight variations. display, for instance, works a little differently; both browser
and minimal-ui
are forced into a minimal-ui
display, and fullscreen
and standalone
are both forced into a standalone
display (additional display_override
options work as expected). In addition, there are two more fields that should be included, version
and update_manifest_url
:
version
- Required for Isolated Web Apps. A string consisting of one or more integers separated by a dot (.
). Your version can be something simple like1
,2
,3
, etc…, or something complex like SemVer (1.2.3
). The version number should match the Regex^(\d+.?)*\d$
.update_manifest_url
- Optional, but recommended field that points to an HTTPS URL (orlocalhost
for testing) where a Web Application Update Manifest can be retrieved.
A minimal Web App Manifest for an Isolated Web App may look something like this:
Web application update manifest
A Web Application Update Manifest is a JSON file that describes each version of a given web application. The JSON object contains one required field, version
, which is a list of objects containing version
, src
, and channels
:
version
- The version number of the application, same as the Web App Manifest’sversion
fieldsrc
- The HTTPS URL (orlocalhost
for testing) pointing to the hosted bundle for that version (the.swbn
file). Relative URLs are relative to the Web Application Update Manifest file.channels
- A list of strings to identify the update channel this version is part of. A specialdefault
channel is used to describe the primary channel that will be used if no other channel is selected.
You can also include a channels
field, an object of your channel IDs with an optional name
property for each ID to provide a human-readable name (including for the default
channel. A channel that doesn’t include the name
property, or isn’t included in the channels
object, uses its ID as its name.
A minimal update manifest may look something like this:
In this example, there are three channels: default
that will be labeled Stable
, 5-lts
that will be labeled 5.x Long-term Stable
, and next
that will be labeled next
. If a user is on channel 5-lts
, they’ll get version 5.2.17
, if they’re on channel default
they’ll get version 5.3.0
, and if they’re on channel next
they’ll get version 5.3.1
.
Web Application Update Manifests can be hosted on any server. Updates are checked for every 4-6 hours.
Admin managed
For their initial launch, Isolated Web Apps will only be able to be installed on Chrome Enterprise managed Chromebooks by an administrator through the Admin panel.
To get started, from your Admin panel, go to Devices > Chrome > Apps & extensions > Users & browsers. This tab allows you to add apps and extensions from the Chrome Web Store, Google Play, and the web for users across your organization. Adding items is done by opening the yellow floating add (+
) button on the bottom right corner of the screen and selecting the type of item to add.
When opened, there will be an icon of a square inside a square, labeled Add an Isolated Web App. Clicking on it will open a modal to add an IWA to you OU. To do so, you’ll need two pieces of information: the IWA’s Web Bundle ID (generated from your app’s public key and displayed after the app is bundled and signed) and the URL to the Web App Update Manifest for the IWA. Once installed, you’ll have the Admin panel’s standard set of options for managing it:
- Installation policy - How you want the IWA installed, either force installed, force installed and pinned to the ChromeOS shelf, or prevent installation.
- Launch on login - How you want the IWA launched, either allow a user to launch manually, force the IWA to launch when the user logs in, but let them close it, or force launch when the user logs in and prevent it from closing.
Once saved, the app will be installed the next time a policy update is applied to Chromebooks in that OU. Once installed, a user's device will check for updates from the Web App Update Manifest every 4-6 hours.
In addition to force-installing IWAs, you can also auto-grant some permissions for them in a similar way you can for other web applications. To do so, go Devices > Chrome > Web capabilities and click the Add origin button. In the Origin / site pattern field
, paste in the IWA’s Web Bundle ID (isolated-app://
will automatically be added onto it as its protocol). From there, you can set access levels to different APIs (allowed/blocked/unset), including window management, local font management, and the screen monitoring API. For APIs that may require additionyal opt-in from an administrator to enable, like the mandatory screen monitoring API, an additional dialog will pop up to confirm your selection. Once you’re done, save your changes and your users will be ready to start using your IWA!
Working with extensions
While Isolated Web Apps don't work with extensions out of the box, you can connect extensions you own to them. To do so, you'll need to edit the extension's manifest file. The externally_connectable
section of the manifest defines which external web pages or other Chrome Extensions your extension can interact with. Add your IWA's origin under the matches
field within externally_connectable
(be sure. to include the isolated-app://
scheme):
While this will allow your extension to run in the Isolated Web App, it won't allow it to inject content into it; you're limited to passing messages between the extension and your IWA.