☁️ Web・クラウド構築

Isolated Web Apps(IWA)解説:署名付きWeb Bundleの仕組みとunframed時計デモ検証

#Isolated Web Apps#Chrome#ChromeOS#PWA#Web Bundle#Webセキュリティ

1. はじめに

Web の最大の強みは URL を開くだけで即座にアクセスできる手軽さですが、この「URL を踏んだだけで誰でも到達できる(drive-by web)」という特性はセキュリティ上の大きな制約でもあります。生の TCP / UDP 通信を行う Direct Sockets API、Web コンテンツを高度に制御して埋め込む Controlled Frame API、OS のウィンドウ枠を消し去る Unframed display mode といった強力な機能を、通常の Web ページで安全に開放することは困難です。

また、企業や開発者からは「実行時にリモートサーバーを信頼したくない」「暗号学的に署名されたスタンドアロンパッケージとしてバージョン管理・配布したい」というニーズも存在します。こうした背景から登場したのが、Web プラットフォーム上に「隔離・バンドル・バージョン管理・署名・信頼」されたアプリケーションモデルを構築する Isolated Web Apps(IWA) です。

本記事では、ChromeOS および Chrome における IWA のアーキテクチャと「Secure by design」の 3 本柱、更新・配布の仕組みを整理します。後半では、背景透過を前提にデザインしたデスクトップ時計「Glass Clock」を実際に IWA としてビルド・署名・インストールし、unframed 表示モードやウィンドウ透過の実挙動を検証します。


2. IWA とは

IWA は従来の Web サイトや PWA を置き換えるものではなく、Web における「信頼のスペクトラム(Spectrum of trust)」の右端を担う選択肢です。なお、通常の Web(https://)と IWA(isolated-app://)はオリジンや配信方式に互換性がなく、後から相互に移行することはできません。

比較項目Drive-by Web(通常の Web)インストール型 PWAIsolated Web Apps(IWA)
配信とレンダリングHTTPS で都度取得(SSR 可)HTTPS + SW キャッシュ(SSR 可)Signed Web Bundle(.swbn)のみ(SSR 不可・SSG/CSR 前提)
オリジンと同一性証明https://<domain>(DNS / TLS 証明書)https://<domain>(DNS / TLS 証明書)isolated-app://<Web Bundle ID>(オフライン秘密鍵の電子署名)
Manifest の配置任意(<link rel="manifest">)任意(<link rel="manifest">)/.well-known/manifest.webmanifest に固定
CSP / 分離ポリシー開発者が任意に設定開発者が任意に設定ブラウザが厳格な CSP と Cross-Origin Isolation を強制
権限の既定値実行時にプロンプト要求可実行時にプロンプト要求可既定で全ブロック(permissions_policy で明示オプトイン)
利用できる高度 API標準 Web API のみファイル関連付け等の拡張Direct Sockets / Controlled Frame / Unframed 等

Chrome 公式ドキュメントでは、リーチの広さや鍵管理の負担、デプロイの柔軟性から「要件を満たせる限り、可能な限り最も低い信頼モデル(通常の Web や PWA)をデフォルトとして選ぶべき」と強く推奨されています。IWA は専用の高信頼 API や厳格なオフライン署名モデルが本当に必要なケースに絞って採用します。

現在の提供状況は以下の通りです。

  • サポート対象:初期リリースで正式サポートされているのは Chrome Enterprise 管理下の ChromeOS デバイスおよび一部の開発パートナー環境です(非管理デバイスや Windows / macOS / Linux への本格拡大は将来予定。なお Chrome Enterprise ヘルプには Windows 等で IsolatedWebAppInstallForceList ポリシーにより強制インストールできる旨の記載があります)。
  • 対応バージョン:Chrome / ChromeOS 120 以降で開発者モードでのテストが可能になり、ChromeOS 128 で初期リリースされました。
  • IWA allowlist 制限:Chrome 143 以降(ChromeOS)、管理コンソール経由でインストール・更新できるのは審査・登録された「IWA allowlist」掲載アプリのみに制限されています(開発者モードであれば自作アプリのテストは引き続き可能です)。

3. Secure by design の 3 本柱

IWA が高信頼 API を安全に扱えるのは、Packaged・Isolated・Locked down の 3 本柱によって設計段階からの安全性(Secure by design)を強制しているためです。

3-1. Packaged:Signed Web Bundle と公開鍵ベースのオリジン

IWA ではライブサーバーから HTML や JS を配信することはできず、全リソースを 1 つの Signed Web Bundle(.swbn) にまとめる必要があります。すべてのページをバンドル内からオフライン配信するため SSR は利用できず、SSG または CSR(SPA)が基本となります。

通常の Web の TLS 秘密鍵はオンラインサーバー上に置く必要がありますが、.swbn(Web Bundle .wbn の先頭に署名入りの integrity block を連結した形式)の署名鍵はオフライン環境で安全に保管でき、サーバー侵害によるコード改ざんや DNS ハイジャックのリスクを排除できます。

アプリの識別子にはドメインを使わず、署名鍵の公開鍵から Base32 エンコードで導出される Web Bundle ID と isolated-app:// スキーム(例:isolated-app://ggx2sheak3vpmm7vmjqnjwuzx3xwot3vdayrlgnvbkq2mp5lg4daaaic/)が使われます。

署名鍵には、SSH の Ed25519 鍵と同様に短く高速で安全な Ed25519 が扱いやすい選択肢です。OpenSSL で鍵を生成・暗号化し、dist/ ディレクトリの静的ファイルから wbn と wbn-sign で .swbn を作成する手順は以下の通りです(パスフレーズは WEB_BUNDLE_SIGNING_PASSPHRASE 環境変数でも渡せます)。

Terminal
# 1. Ed25519 秘密鍵を生成し、PKCS#8 形式でパスフレーズ暗号化して平文鍵を削除
openssl genpkey -algorithm Ed25519 -out private_key.pem
openssl pkcs8 -in private_key.pem -topk8 -out encrypted_key.pem
rm private_key.pem

# 2. dist/ から未署名バンドル (out.wbn) を作り、秘密鍵で署名して signed.swbn を生成
npx wbn --dir dist -o out.wbn
npx wbn-sign -i out.wbn -k encrypted_key.pem -o signed.swbn

3-2. Isolated:オリジン分離・強制 CSP・ディープリンク制限

IWA はブラウザの通常タブで開くことはできず、必ずスタンドアロンウィンドウ(独立ウィンドウ)として実行されます。isolated-app://<Web Bundle ID> オリジンで動作するため、自社サイト(https://)や他の IWA とも同一オリジンポリシーによって Cookie や localStorage 等のストレージが完全に分離されます。

さらに、通常は開発者がセキュリティヘッダーとして任意に設定する CSP や Cross-Origin Isolation が、IWA ではブラウザ側で無条件に強制適用されます。開発時に特に影響する制約は以下の 4 点です。

  • インラインスクリプトと外部 JS の禁止:script-src 'self' 'wasm-unsafe-eval' が強制され、ここでの 'self' は Signed Web Bundle 内のリソースのみを指します。外部 CDN のスクリプト、HTML 内のインライン <script>、eval() はすべて禁止です(Wasm は実行可能)。
  • Trusted Types の強制:require-trusted-types-for 'script' により、element.innerHTML = ... 等に生文字列を代入するとエラーになります(textContent や DOM API を使うか、Trusted Types ポリシーを通す必要があります)。
  • 外部フォント URL の禁止:font-src 'self' blob: data: のため、Google Fonts 等を外部 https: から直接読み込むことはできません。フォントはバンドルに同梱します。
  • COEP による外部リソースの CORP / CORS 要件:Cross-Origin-Opener-Policy: same-origin や Cross-Origin-Embedder-Policy: require-corp 等が強制されるため、外部 HTTPS の画像やメディアを読み込む場合も配信元サーバー側で適切な CORP または CORS ヘッダーが必要です。

また、想定外の順序で内部ページへ直接遷移させるシーケンスブレイキング攻撃を防ぐため、外部からの任意 URL へのディープリンクは禁止されています。外部から起動・遷移できるのは Manifest で定義した start_url、protocol_handlers、share_target、launch_handler のみに制限されます。

3-3. Locked down:permissions_policy による権限のオプトイン

IWA は既定ですべての権限リクエストをブロックします。位置情報やフルスクリーンなどの権限を使用したい場合は、トップレベルおよび子フレーム(iframe や Controlled Frame)が使う権限を Web App Manifest の permissions_policy に明示してオプトインする必要があります。

JSON
{
  "name": "Fleet Tracker IWA",
  "permissions_policy": {
    "geolocation": ["self", "https://map.example.com"],
    "fullscreen": ["*"]
  }
}

ここに記述してもインストール時に権限が自動付与されるわけではなく、あくまで「実行時に権限をリクエストすることが許可される(ブロックが解除される)」状態になります。また、Web Bundle 内の HTML レスポンスに Permissions-Policy ヘッダーも含めた場合は両者の積集合(両方で許可されたディレクティブとオリジン)のみが有効となり、インストール前に要求権限を監査できる仕組みになっています。


4. バージョン管理と配布

4-1. マニフェストの固定配置と Update Manifest

IWA では HTML から <link rel="manifest"> でリンクせず、バンドル内の /.well-known/manifest.webmanifest という固定パスにマニフェストを配置します。通常の PWA と異なり version フィールドが必須(1.0.0 のようなドット区切りの整数列)です。また、display は browser を指定しても minimal-ui に強制され、standalone や display_override(unframed 等)が利用できます。

アップデートを配信するには、マニフェストの update_manifest_url が指す HTTPS サーバー上に Update Manifest(JSON) と新しい .swbn を配置します。

JSON
{
  "channels": {
    "next": { "name": "Next / Beta Channel" }
  },
  "versions": [
    {
      "version": "5.3.0",
      "src": "https://example.com/iwa/app-5.3.0.swbn",
      "channels": ["next", "default"]
    },
    {
      "version": "5.3.1",
      "src": "https://example.com/iwa/app-5.3.1.swbn",
      "channels": ["next"]
    }
  ]
}

端末は既定で default チャンネルを購読するため上記では 5.3.0 が適用され、next チャンネルを購読する端末にのみ先行して 5.3.1 が配信されます。インストール済みの IWA はバックグラウンドで 4〜6 時間ごとに更新チェックを行います(chrome://web-app-internals から手動更新も可能です)。

4-2. Google 管理コンソールでの配布と IWA 専用 API

Chrome Enterprise 環境では、Google 管理コンソールの [デバイス]>[Chrome]>[アプリと拡張機能]>[ユーザーとブラウザ] から対象 OU を選び、右下の「+」ボタンの 「Add an Isolated Web App」 に Web Bundle ID と Update Manifest URL を登録して配布します。インストールポリシー(強制インストール、シェルフ固定、ブロック)やログイン時の自動起動(閉じられないキオスク風稼働を含む)も設定可能です。また、管理コンソールの [Web capabilities] に Web Bundle ID を登録すれば、Window management や Local font access、Screen monitoring といった権限をダイアログなしで管理者側から自動付与(allowed)できます。

IWA で利用できる代表的な高度 API と Chrome 152 の新機能は以下の通りです。

  • Direct Sockets API:任意のホスト・ポートに対して生の TCP / UDP ソケット通信を直接行う API。
  • Controlled Frame API:任意の Web コンテンツを埋め込み、ナビゲーション制御やスクリプト注入を行える <webview> 相当の API。
  • Sub apps(Chrome 152):1 つの IWA 配下に複数のサブアプリを登録・管理できる仕組み。
  • Unframed display mode(Chrome 152):OS のウィンドウ枠やタイトルバーを消し、Web コンテンツでウィンドウ全体を占有する display_override の表示モード(Window Management 権限が必須)。
  • window-drag CSS プロパティ(Chrome 152):従来の -webkit-app-region: drag を標準化・改名したもので、move(ドラッグ移動)または none(クリック操作対象)を指定してドラッグ領域を定義するプロパティ。

5. デモ:背景透過の時計「Glass Clock」

ここからは、Chrome 152 の unframed 表示モードと window-drag を試すため、文字盤の背景を持たないアナログ+デジタル時計「Glass Clock」を IWA として作成し、OS のデスクトップ上でもタイトルバーが消えて背景が透過するかを検証します。

Glass Clock のライブプレビュー。iframe の背景が透過しているため、背後のグラデーション壁紙が透けて見えます(ホバーで右上に操作ボタン。◐で文字色を白/黒に切り替え)

5-1. 構成とマニフェスト・実装のポイント

リポジトリ内では、アプリ本体は public/demos/iwa-clock/、署名ツールは demos/iwa-transparent-clock/ に配置しています(.gitignore に keys/ と dist/ を指定し、秘密鍵のコミットを防止しています)。

public/demos/iwa-clock/.well-known/manifest.webmanifest の要点は以下の通りです。display_override で "unframed" を最優先に指定し、それに必要な "window-management" 権限を permissions_policy でオプトインしています。

JSON
{
  "name": "Glass Clock",
  "version": "1.0.2",
  "display": "standalone",
  "display_override": ["unframed", "borderless", "standalone"],
  "permissions_policy": {
    "window-management": ["self"]
  }
}

CSS(style.css)では、タイトルバーのない unframed ウィンドウでも移動できるよう時計全体にドラッグ領域を指定し、右上の操作ボタン(文字色切替・閉じる)だけをドラッグ対象から外しています。また、IWA 起動時用の背景を @media (display-mode: ...) で定義しています。

CSS
.clock {
  app-region: drag;
  -webkit-app-region: drag;
  window-drag: move;
}

.controls {
  app-region: no-drag;
  -webkit-app-region: no-drag;
  window-drag: none;
}

@media (display-mode: standalone), (display-mode: unframed), (display-mode: borderless) {
  html {
    background: radial-gradient(120% 120% at 30% 20%, #2a2e6b 0%, #16181d 70%);
  }
}

JavaScript(clock.js)は外部モジュールとして読み込み、以下の 2 点で強制 CSP に対応しています。

  • innerHTML 不使用で Trusted Types を回避:デジタル時刻の更新には textContent、針の描画には Canvas 2D API のみを使い、require-trusted-types-for 'script' のエラーを回避しています。
  • Canvas を毎フレーム透明クリア:ctx.clearRect() で Canvas 全体を透明に戻してから目盛りと針だけを描画し、文字盤の塗りつぶしを行いません。

5-2. ビルドと Chrome へのインストール手順

demos/iwa-transparent-clock/ では、wbn(0.0.10)と wbn-sign(0.3.3、Node 22.13 以降が必要)を使ってバンドル内の URL を "/" 起点で登録し、.swbn を出力します。

Terminal
cd demos/iwa-transparent-clock
npm install && npm run keygen && npm run build
Text
Signed Web Bundle : dist/glass-clock.swbn
Web Bundle ID     : 3nuzhl3u4jgrf5reo6ph3z4miarck7qo3jj7os6wfxkgmnigjuqqaaic
IWA Origin        : isolated-app://3nuzhl3u4jgrf5reo6ph3z4miarck7qo3jj7os6wfxkgmnigjuqqaaic/

生成した glass-clock.swbn を Chrome にインストールする GUI 手順は以下の 3 ステップです。

  1. chrome://flags/#enable-isolated-web-app-dev-mode を開き、「Isolated Web App Developer Mode」を Enabled にして Chrome を再起動します。
  2. chrome://web-app-internals を開き、「Install IWA from Signed Web Bundle」 から dist/glass-clock.swbn を選択してインストールします。
  3. インストールされた「Glass Clock」のアプリ設定(または権限プロンプト)で、「ウィンドウ管理(Window management)」権限を「許可」に切り替えます。

5-3. 検証結果

2026年11月1日、非管理端末の macOS 上の Google Chrome 156.0.8078.80 で検証したスクリーンショットが以下です。

macOS 上で unframed 表示の IWA として動く Glass Clock。タイトルバーは消えているが、背景は暗いグラデーションで塗られている
macOS / Chrome 156 で unframed 表示の IWA として動く Glass Clock。タイトルバーは消えたが、ウィンドウ背景は不透明なため display-mode メディアクエリで暗い背景を敷いている(上部は開発者モードフラグの警告バー)

実機検証で確認できた挙動は以下の通りです。

検証項目結果と詳細
インストールとオリジン分離成功。isolated-app://3nuzhl3u... オリジンの独立ウィンドウで起動し、Finder の「Chrome アプリ」にも登録された。
unframed によるタイトルバー消去成功。Window management 権限の許可後に matchMedia('(display-mode: unframed)').matches が true となり、上部のタイトルバーが完全に消えた(macOS の赤・黄・緑ボタンのみ残る)。
OS レベルのウィンドウ背景透過不可。CSS と Canvas を透明にしても Chrome のネイティブウィンドウ層が背面を alpha = 1.0 の完全不透明な白で塗りつぶしてしまう。Manifest の background_color を "transparent" や "#00000000" にしても変化なし。
ドラッグ移動とボタンクリック未確認。今回の検証はスクリーンショットと DevTools Protocol による自動確認で行ったため、実際のドラッグ移動やボタンのクリック操作は確かめていない。
開発者モードフラグなしでの起動不可。インストール後に IsolatedWebAppDevMode フラグを外して起動し直すと、中身が一切描画されず空のウィンドウになった。

6. IWA を選ぶべきケース

ここまでの仕様と検証結果を踏まえ、IWA を選ぶべきケースと代替案を整理します。

判断の軸IWA を選ぶべきケースIWA を選ぶべきでないケース(代替案)
利用したい APIDirect Sockets(TCP / UDP 直接通信)、Controlled Frame、Unframed ウィンドウなど IWA 専用の高信頼 API が必須。標準 Web API や通常の PWA の機能だけで要件を満たせる → 通常の Web / PWA。
配布先と運用モデルChrome Enterprise 管理下の ChromeOS / Chrome 向けに、オフライン署名された .swbn を管理コンソールで統制配布したい。一般コンシューマーに URL で広く届けたい、または SSR やサーバー側での即時デプロイが不可欠 → 通常の Web / PWA。
ウィンドウ透過・OS 統合タイトルバーを消した不透明な unframed ウィンドウで独自 UI を構成したい。OS レベルのウィンドウ背景透過やトレイ常駐などが必要 → Electron / Tauri。

7. まとめ

最後に、本記事で解説した Isolated Web Apps(IWA)の要点を振り返ります。


📚 関連する技術記事

Web・クラウド構築の記事一覧 →

HTML-in-Canvas 解説:canvas に本物の HTML を描く新 API と、ブラウン管風レトロコンソールのデモ

<canvas> の中に DOM を描画できる HTML-in-Canvas(drawElementImage / texElementSubImage2D / paint イベント)の仕組みと Chrome での提供状況を解説し、WebGL シェーダーで走査線・湾曲・残光を再現したブラウン管風コンソールのデモを実装・検証します。

Cloudflare D1とは?従来型RDBMS・Cloud SQLとの徹底比較からコスト・パフォーマンス・設計の勘所まで解説

Cloudflareのエッジサーバーレスデータベース「Cloudflare D1」の内部アーキテクチャと性質を徹底解説。従来型RDBMS(PostgreSQL/MySQL)やGoogle Cloud SQL・AWS RDSとの比較、圧倒的なコスト優位性、レイテンシとパフォーマンスの真実、向いている用途・制約まで体系的にまとめます。