# Isolated Web Apps

> What an Isolated Web App is: a signed Web Bundle on the isolated-app scheme, the manifest fields it adds, the policies applied to it, and where it runs.

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.

## How it works

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.

### Packaging and signing

Google's ChromeOS documentation,
[Isolated Web Apps](https://chromeos.dev/en/web/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.

### Identity and the isolated-app scheme

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](https://github.com/WICG/isolated-web-apps/blob/main/Scheme.md) describes
the `isolated-app:` scheme and signed web bundle IDs, and the
[Updates Explainer](https://github.com/WICG/isolated-web-apps/blob/main/Updates.md) 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.

### Manifest fields and updates

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.

### Policies applied to the app

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.

:::security
The ChromeOS page states that because Web Bundles can carry Permissions Policy headers too,
only permissions declared in both the manifest field and the bundle headers are allowed,
and then only the origins present in both allowlists
([Isolated Web Apps](https://chromeos.dev/en/web/isolated-web-apps), chromeos.dev). The
manifest field and the bundle headers are separate places to keep in step.
:::

## Availability

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](https://wicg.github.io/isolated-web-apps/isolated-contexts.html) 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.

## Examples

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

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.

```json
{
  "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](https://github.com/WICG/isolated-web-apps/blob/main/Updates.md) 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

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

```json
{
  "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.

### Detecting the model at runtime

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.

```js
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.

## Observed behaviour

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

:::observed
[Isolated Web Apps](https://chromeos.dev/en/web/isolated-web-apps) (chromeos.dev) documents
the development flow by exact string: enable the flag
`chrome://flags/#enable-isolated-web-app-dev-mode`, then open `chrome://web-app-internals`,
which offers the two actions **Install IWA via Dev Mode Proxy** and **Install IWA from
Signed Web Bundle** (English UI). After signing, the tool prints the identity as:

```text
Web Bundle ID: ggx2sheak3vpmm7vmjqnjwuzx3xwot3vdayrlgnvbkq2mp5lg4daaaic
Isolated Web App Origin: isolated-app://ggx2sheak3vpmm7vmjqnjwuzx3xwot3vdayrlgnvbkq2mp5lg4daaaic/
```
:::

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.

## See also

- [Installability criteria](/reference/installation/installability-criteria/)
- [Trusted Web Activity](/reference/installation/twa/)
- [PWAs on ChromeOS (launcher and shelf)](/reference/platforms/chromeos/)
- [display manifest member](/reference/manifest/display/)
- [Isolated Web Apps Explainer](https://github.com/WICG/isolated-web-apps) (github.com)
- [Isolated Web Apps Updates Explainer](https://github.com/WICG/isolated-web-apps/blob/main/Updates.md) (github.com)
- [Isolated Web Apps](https://chromeos.dev/en/web/isolated-web-apps) (chromeos.dev)
- [Isolated Contexts](https://wicg.github.io/isolated-web-apps/isolated-contexts.html) (wicg.github.io)