ADR-004: セキュリティモデル — サンドボックス化されたiframe + HTMX
HyperGenはエージェント生成HTMLをサンドボックス化されたiframe内にレンダリング。HTMXがサンドボックス内でインタラクティビティを処理し、postMessageがiframeとホストをブリッジ。
ステータス: 承認済み 日付: 2026-03-28
コンテキスト
HyperGenのコア設計(ADR-001)は、AIエージェントがHTMX属性付きのHTMLを生成することを確立しています。このHTMLはクライアントアプリケーションのどこかにレンダリングされなければなりません。レンダリング戦略はセキュリティに直接影響します:
- 直接DOMインジェクション(ホストページにHTMLを注入)— 最大の統合性だが、エージェント生成HTMLがホストのDOM、Cookie、localStorage、すべてのJavaScript APIにフルアクセス。生成HTML内の単一XSSベクターでアプリケーション全体が危殆化。
- サンドボックス化されたiframe(分離されたiframe内にHTMLをレンダリング)— 強力なセキュリティ境界。生成HTMLはホストのDOMやストレージにアクセス不可。統合には
postMessage経由の明示的なブリッジが必要。
ClaudeのGenerative UIとMCP Appsはどちらもサンドボックス化されたiframeを使用しています。このアプローチは実戦で検証済みであり、ブラウザのセキュリティプリミティブと一致しています。
決定
HyperGenはエージェント生成UIをサンドボックス化されたiframe内にレンダリングします。HTMXはiframe内で動作し、HTTP/SSE経由でエージェントサーバーと直接通信。ホストアプリケーションはiframeとpostMessage経由で通信。
アーキテクチャ
┌─────────────────────────────────────────────┐
│ ホストアプリケーション │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ サンドボックス化された <iframe> │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ HTMX │ │ エージェント │ │ │
│ │ │ (14KB) │ │ HTML+CSS+JS │ │ │
│ │ └──────┬──────┘ └─────────────┘ │ │
│ │ │ │ │
│ │ │ hx-get / hx-post / SSE │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ │ │
│ │ │ エージェント │ (HTTP / SSE) │ │
│ │ │ サーバー │ │ │
│ │ └─────────────┘ │ │
│ │ │ │
│ └────────────────┬──────────────────────┘ │
│ │ postMessage │
│ ▼ │
│ ┌─────────────────────────────────────────┐│
│ │ ホストJavaScript(イベントリスナー) ││
│ └─────────────────────────────────────────┘│
└─────────────────────────────────────────────┘3つの通信チャネル
| チャネル | 方向 | 目的 | メカニズム |
|---|---|---|---|
| エージェント → iframe | サーバー → クライアント | UIフラグメントのストリーム、インタラクションへの応答 | HTTPレスポンス、HTMX経由のSSE |
| iframe → エージェント | クライアント → サーバー | ユーザーアクション送信(フォーム送信、ボタンクリック) | HTMX hx-get/hx-post(標準HTTP) |
| iframe ↔ ホスト | 双方向 | テーマ注入、リサイズ通知、ナビゲーション、データ受け渡し | postMessage API |
Sandboxポリシー
iframeはHTMXが必要とするパーミッションのみの厳格なsandbox属性を使用:
<iframe
sandbox="allow-scripts allow-same-origin allow-forms"
src="about:blank"
style="width: 100%; border: none;"
></iframe>| パーミッション | 必要な理由 |
|---|---|
allow-scripts | HTMXがhx-*属性を処理するためにJavaScriptが必要 |
allow-same-origin | HTMXがエージェントサーバーにHTTPリクエストを行う必要(同一オリジンまたはCORS) |
allow-forms | HTMXのフォーム送信(<form>でのhx-post) |
明示的に付与されないもの:
| パーミッション | 拒否理由 |
|---|---|
allow-top-navigation | iframeがホストページをリダイレクトするのを防止 |
allow-popups | iframeが新しいウィンドウを開くのを防止 |
allow-modals | alert()、confirm()、prompt()がホストをブロックするのを防止 |
allow-pointer-lock | カーソルキャプチャを防止 |
allow-downloads | 自動ファイルダウンロードを防止 |
Content Security Policy
ホストアプリケーションはiframeコンテンツにCSPを設定して機能をさらに制限すべきです (SHOULD):
default-src 'none';
script-src 'self' 'unsafe-inline' https://unpkg.com/htmx.org;
style-src 'self' 'unsafe-inline';
connect-src <agent-server-origin>;
img-src 'self' data: https:;
font-src 'self' data:;主な制限:
script-srcはインラインスクリプトと信頼されたCDNからのHTMXのみを許可connect-srcはHTTP/SSE接続を既知のエージェントサーバーに制限eval()なし、HTMX以外の外部スクリプトロードなし
iframeのライフサイクル
1. 初期化
ホストがサンドボックス化されたiframeを作成し、最小限のブートストラップドキュメントを注入:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/htmx.org@2"></script>
<script src="https://unpkg.com/htmx-ext-sse@2"></script>
<style>
:root {
/* Host theme variables injected here */
}
* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
</style>
</head>
<body>
<div id="hg-root"
hx-ext="sse"
sse-connect="/api/agent/stream"
sse-swap="message"
hx-swap="innerHTML">
<!-- Agent-generated content streams here -->
</div>
<script>
// postMessage bridge for host communication
window.addEventListener('message', (e) => {
if (e.data.type === 'hg:theme') {
Object.entries(e.data.vars).forEach(([k, v]) => {
document.documentElement.style.setProperty(k, v);
});
}
});
// Notify host of size changes for auto-resize
const ro = new ResizeObserver(() => {
window.parent.postMessage({
type: 'hg:resize',
height: document.body.scrollHeight
}, '*');
});
ro.observe(document.body);
</script>
</body>
</html>2. ストリーミング
SSE接続(sse-connect)がエージェントサーバーからHTMLフラグメントをストリーミング。HTMXが処理:
- フラグメント到着時のプログレッシブレンダリング
hx-swap戦略(innerHTML、beforeend、outerHTMLなど)によるDOM更新hx-indicatorによるローディングインジケーター
3. ユーザーインタラクション
ユーザーが生成されたUI(ボタンクリック、フォーム送信)を操作すると、HTMXがエージェントサーバーに標準HTTPリクエストを送信。サーバーはHTMXがDOMにスワップする新しいHTMLフラグメントで応答。エージェントインタラクションにpostMessageのラウンドトリップは不要 — これはiframe ↔ サーバーの直接ループです。
4. ホスト通信
iframe境界を越える必要がある操作:
// iframe内: ナビゲーション意図をホストに通知
window.parent.postMessage({
type: 'hg:navigate',
url: '/dashboard'
}, '*');
// iframe内: ホストにデータを送信
window.parent.postMessage({
type: 'hg:data',
payload: { selectedItem: 42 }
}, '*');// ホスト: iframeイベントをリッスン
iframe.contentWindow.addEventListener('message', (e) => {
if (e.data.type === 'hg:navigate') {
router.push(e.data.url);
}
});
// ホスト: iframeにテーマを注入
iframe.contentWindow.postMessage({
type: 'hg:theme',
vars: {
'--hg-surface': '#1a1a2e',
'--hg-text': '#e0e0e0',
'--hg-accent': '#7c3aed',
}
}, '*');HyperGen vs. MCP Appsセキュリティ比較
| 側面 | MCP Apps | HyperGen |
|---|---|---|
| 分離 | サンドボックス化されたiframe | サンドボックス化されたiframe(同じ) |
| インタラクティビティ | iframe内のJavaScript(任意) | HTMX属性(宣言的) + 最小限JS |
| エージェント通信 | JSON-RPC over postMessage → ホスト → サーバー | iframe → エージェントサーバーへの直接HTTP/SSE |
| ストリーミング | 非サポート(完全ペイロード配信) | HTMX経由のSSE(プログレッシブレンダリング) |
| プロトコル結合 | MCP必須 | プロトコル非依存(HTTP + SSEのみ) |
| ホストブリッジ | JSON-RPC 2.0 over postMessage | 型付きイベントによるシンプルなpostMessage |
主要なアーキテクチャの違い: MCP Appsはすべてのエージェント通信をホスト経由でルーティング(iframe → postMessage → ホスト → MCPサーバー)。HyperGenはiframeがHTTP/SSE経由でエージェントサーバーと直接通信し、postMessageはホスト統合(テーマ、ナビゲーション、データ受け渡し)のみに使用。これはよりシンプルでストリーミングを可能にします。
脅威モデル
iframeサンドボックスで軽減される脅威
| 脅威 | 軽減方法 |
|---|---|
| ホストに対するXSS | iframeサンドボックスがホストのDOM、Cookie、localStorageへのアクセスを防止 |
| Cookie窃取 | sandboxでallow-same-originなしではCookieを防止; ありの場合はiframeオリジンにスコープ |
| ホストページナビゲーション | allow-top-navigationが付与されない |
| ポップアップスパム | allow-popupsが付与されない |
| ホスト入力のキーロギング | iframeはホストのDOMイベントにアクセス不可 |
残存リスク
| リスク | 深刻度 | 軽減策 |
|---|---|---|
| 悪意あるエージェントサーバー | 中 | エージェントサーバーURLはホストが設定。CSP connect-srcで信頼されたエンドポイントのみ許可すべきである (SHOULD)。 |
| HTTP経由のデータ漏洩 | 中 | CSP connect-srcが外向き接続を設定されたエージェントサーバーのみに制限。 |
| iframeフィッシング | 低 | iframeがホストのログインフォームに見えるコンテンツをレンダリングする可能性。軽減: ホストはiframe領域を視覚的に区別すべき(薄いボーダー、ラベルなど)。 |
| リソース枯渇 | 低 | 悪意あるHTMLがCPU/メモリを消費する可能性。軽減: ホストはiframeサイズ制限を設定し、応答しないiframeを破棄可能。 |
allow-same-originリスク | 中 | allow-same-originにより、iframeがそのオリジンのCookie/ストレージにアクセス可能。エージェントサーバーがホストと同一オリジンの場合、アクセス範囲が広がる。軽減: エージェントサーバーは別のサブドメインにすべきである (SHOULD)、またはCORSを使用。 |
結果
ポジティブ
- デフォルトで強力なセキュリティ。 iframeサンドボックスはブラウザが強制するセキュリティ境界。サニタイズライブラリ不要 — ブラウザが分離を行う。
- ストリーミングが動作。 MCP Appsと異なり、HTMXのSSE拡張がpostMessageオーバーヘッドなしにiframe内に直接ストリーミング。
- シンプルなホスト統合。 postMessageは理解しやすい最小限のAPI。JSON-RPCの複雑さなし。
- エコシステムに馴染みがある。 ClaudeやMCP Appsと同じパターンに従い、既にそのエコシステムにいる開発者が採用しやすい。
ネガティブ
- iframeレンダリングのオーバーヘッド。 各HyperGenウィジェットが新しいブラウジングコンテキストを作成。多くの小さなウィジェットがあるアプリケーションではパフォーマンスに影響する可能性。
- CSSの分離は利点でありコストでもある。 iframeのスタイルは完全に分離 — ホストのCSSが漏れ込まないが、ホストのデザインシステムはpostMessage経由のCSS変数で明示的にブリッジする必要がある。
- クロスオリジンの複雑さ。 エージェントサーバーが異なるオリジンにある場合、CORSの設定が必要。これは標準的だがセットアップの摩擦が増す。
参考資料
- MCP Apps Specification — 構築の基盤となるiframeベースのアプローチ
- HTML
sandboxattribute — ブラウザセキュリティプリミティブ - Content Security Policy — 追加制限レイヤー
- HTMX SSE Extension — iframe内でのストリーミング
- postMessage API — iframe-ホスト通信