Skip to content

Concept in Installation

Isolated Web Apps

Published

An Isolated Web App is an application built with web technologies that is packaged into a Web Bundle, signed by its developer, and distributed to users instead of being hosted on a live web server and fetched over HTTPS. The WICG explainer proposes the model for applications whose threat model includes the application server itself being compromised, and for applications that need capabilities the ordinary web security model cannot grant.

The explainer describes the core of the proposal as making application updates explicit. Unlike TLS keys, which have to be available online to establish new connections, the key used to sign the Web Bundle can be kept securely offline and is used infrequently, and the channel through which updates are distributed creates another point where new resources can be checked for potentially malicious content.

Google’s ChromeOS documentation, Isolated Web Apps, states that pages and assets for an Isolated Web App are not served from live servers and are not fetched over the network like normal web applications, and that the app’s resources are packaged into a Signed Web Bundle, a .swbn file that concatenates the bundled resources with an integrity block. It gives the signing keys as Ed25519 or ECDSA P-256 key pairs, the private key signing the bundle and the public key verifying it.

That same page states a consequence for architecture: a site must be able to serve each of its pages while offline, so server-rendered pages are excluded, and statically generated or client-rendered sites are the architectures to consider.

The explainer gives the reason the identity cannot stay a DNS name: a name is vulnerable to a temporary loss of control over the domain or over the infrastructure used to validate ownership of it, and the authors also wanted to avoid building an alternative to certificate authorities sharing the same namespace. Isolated Web Apps therefore use a new scheme, tentatively isolated-app://, whose authority section is based on the public key used to sign the Web Bundle.

The explainer splits the detail into companion documents: the Scheme Explainer describes the isolated-app: scheme and signed web bundle IDs, and the Updates Explainer describes the version and update_manifest_url manifest fields and the updating process.

The ChromeOS page draws the practical conclusion: because identity is tied to the signing key, each app needs its own key, and the same app signed with different keys is treated as different apps. The signing tool prints the resulting Web Bundle ID and origin, and the page notes that Isolated Web Apps open in their own window without a URL bar, like a PWA, so users are unlikely to see that origin.

The ChromeOS documentation states that an Isolated Web App’s Web App Manifest is not linked from the HTML, because it has to be discoverable at install time: it must be located at /.well-known/manifest.webmanifest and included in the package. It records two additional fields beyond the manifest keys a PWA uses:

Field Status What it holds
version required for Isolated Web Apps a string of one or more integers separated by a dot, matching the regular expression ^(\d+.?)*\d$
update_manifest_url optional but recommended an HTTPS URL, or localhost for testing, where a Web Application Update Manifest can be retrieved

The same page records that display behaves differently in this model: browser and minimal-ui are both forced into a minimal-ui display, fullscreen and standalone are both forced into a standalone display, and additional display_override options work as expected. For downgrade protection, the explainer states that implementations may require either a version manifest field or the signature timestamp to be monotonically increasing.

Both documents list the headers that make the model hold. The explainer gives the Content Security Policy applied to resources loaded from the bundle, in which 'self' refers to resources from the bundle and excludes blob:, filesystem: and other local schemes as well as inline script. It also gives the cross-origin isolation headers Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp, Cross-Origin-Resource-Policy: same-origin and Content-Security-Policy: frame-ancestors 'self'.

Further restrictions follow from that. Both documents state that cross-origin navigations into the application are disallowed to prevent sequence breaking attacks, so the application is launched through a well-defined entry point such as its start_url, a protocol handler, a Share Target, or a launch handler. And the ChromeOS page states that Isolated Web Apps block all permission requests by default, with the developer opting in through a permissions_policy manifest field; adding a permission there does not grant it, it makes the permission available to be requested.

The ChromeOS documentation carries a version note reading ChromeOS version: 128, and states that the initial release of Isolated Web Apps, and any high-trust APIs that require them, will be available to Chrome Enterprise administered ChromeOS devices and select development partners, with an intention to expand to additional partners and to unmanaged and cross-platform devices in the future. For local testing the same page asks for Chrome or ChromeOS 120 or later with the Isolated Web App flags enabled.

The underlying platform concept is specified separately: Isolated Contexts is a Draft Community Group Report, published by the Web Platform Incubator Community Group, that defines isolated contexts so that implementers and specification authors can enable certain features only when minimum standards of isolation and integrity are met. It states that it is not a W3C Standard and is not on the W3C Standards Track.

The snippets below cover the manifest an Isolated Web App needs, the permissions field, and a runtime check that keeps shared code working outside the model.

A minimal manifest for an Isolated Web App

Section titled “A minimal manifest for an Isolated Web App”

The ChromeOS page’s minimal example adds version and update_manifest_url to the keys a PWA manifest already carries, and the file is shipped at /.well-known/manifest.webmanifest inside the bundle rather than linked from the HTML.

{
"name": "IWA Kitchen Sink",
"version": "0.1.0",
"update_manifest_url": "https://example.com/updates.json",
"start_url": "/",
"icons": [
{ "src": "/images/icon.png", "type": "image/png", "sizes": "512x512", "purpose": "any" },
{ "src": "/images/icon-mask.png", "type": "image/png", "sizes": "512x512", "purpose": "maskable" }
]
}

A Web Application Update Manifest is a JSON file describing each version of the application. The Updates Explainer gives it a single JSON object with a top-level versions field holding a list, which is the key the ChromeOS page’s own example uses. Each entry in the list carries at least version and src: version is a string in the format of the manifest’s own version field, and src is a URL resolved relative to the location of the update manifest, which must be an HTTPS URL or a localhost HTTP URL for testing. The explainer marks channels optional — a list of Update Channel identifiers, treated as ["default"] when it is not set.

Declaring the permissions the app may request

Section titled “Declaring the permissions the app may request”

The ChromeOS page’s fleet-tracking example allows the app and one embedded map origin to request geolocation, and any origin to request fullscreen.

{
"permissions_policy": {
"geolocation": ["self", "https://map.example.com"],
"fullscreen": ["*"]
}
}

Because the field makes a permission requestable rather than granted, the app still calls the ordinary permission API and still handles a refusal. A capability absent from this object is blocked before the request reaches the user.

Code shared between a site and its Isolated Web App build needs to know which one it is running in. The scheme and cross-origin isolation are both documented properties of the model, so the check reads them rather than sniffing a user agent.

function runsInIsolatedAppRuntime() {
if (!('crossOriginIsolated' in window)) {
// No cross-origin isolation signal: treat this as an ordinary web page.
return false;
}
return window.crossOriginIsolated && location.protocol === 'isolated-app:';
}
if (runsInIsolatedAppRuntime()) {
document.documentElement.dataset.runtime = 'isolated-app';
} else {
// Ordinary web page: the high-trust APIs are absent, so keep the network code path.
document.documentElement.dataset.runtime = 'web';
}

What the check reports is the runtime, not how the app was delivered. A Dev Mode Proxy install runs on isolated-app: under the same policies while pointing at a development server rather than a bundle, so it answers isolated-app here too. The protocol and crossOriginIsolated signals used above do not separate those two delivery paths, so code that needs to tell a signed bundle from a proxied development install has to carry its own marker, such as a build-time flag.

Use the result to pick a code path, not to assume a capability: a permission still has to be present in both the permissions_policy field and the bundle’s headers before the app can request it.

The development path is reachable without an enterprise enrolment, and the strings below are the ones the documentation prints for it.

The page states that a Dev Mode Proxy install can point at any URL, including a local development server, without bundling it, and that the app installed that way is assigned a random identifier. So an origin observed during development is not the origin the signed build will have.

Specifications

SpecificationStatus
None.