HyperGen

ADR-004: Security Model — Sandboxed Iframe + HTMX

HyperGen renders agent-generated HTML inside sandboxed iframes. HTMX handles interactivity within the sandbox; postMessage bridges the iframe to the host.

Status: Accepted Date: 2026-03-28


Context

HyperGen's core design (ADR-001) establishes that AI agents generate HTML with HTMX attributes. This HTML must be rendered somewhere in the client application. The rendering strategy has direct security implications:

  • Direct DOM injection (inject HTML into the host page) — Maximum integration, but agent-generated HTML has full access to the host DOM, cookies, localStorage, and all JavaScript APIs. A single XSS vector in the generated HTML compromises the entire application.
  • Sandboxed iframe (render HTML in an isolated iframe) — Strong security boundary. The generated HTML cannot access the host DOM or storage. Integration requires explicit bridging via postMessage.

Claude's Generative UI and MCP Apps both use sandboxed iframes. This approach is battle-tested and aligns with browser security primitives.


Decision

HyperGen renders agent-generated UI inside sandboxed iframes. HTMX operates within the iframe, communicating directly with the agent server via HTTP/SSE. The host application communicates with the iframe via postMessage.

Architecture

┌─────────────────────────────────────────────┐
│  Host Application                           │
│                                             │
│  ┌───────────────────────────────────────┐  │
│  │  Sandboxed <iframe>                   │  │
│  │                                       │  │
│  │  ┌─────────────┐    ┌─────────────┐  │  │
│  │  │  HTMX       │    │  Agent HTML  │  │  │
│  │  │  (14KB)     │    │  + CSS + JS  │  │  │
│  │  └──────┬──────┘    └─────────────┘  │  │
│  │         │                             │  │
│  │         │ hx-get / hx-post / SSE      │  │
│  │         ▼                             │  │
│  │  ┌─────────────┐                     │  │
│  │  │ Agent Server│ (HTTP / SSE)        │  │
│  │  └─────────────┘                     │  │
│  │                                       │  │
│  └────────────────┬──────────────────────┘  │
│                   │ postMessage               │
│                   ▼                           │
│  ┌─────────────────────────────────────────┐│
│  │  Host JavaScript (event listeners)      ││
│  └─────────────────────────────────────────┘│
└─────────────────────────────────────────────┘

Three Communication Channels

ChannelDirectionPurposeMechanism
Agent → IframeServer → ClientStream UI fragments, respond to interactionsHTTP responses, SSE via HTMX
Iframe → AgentClient → ServerSend user actions (form submit, button click)HTMX hx-get/hx-post (standard HTTP)
Iframe ↔ HostBidirectionalTheme injection, resize notifications, navigation, data passingpostMessage API

Sandbox Policy

The iframe uses a strict sandbox attribute with only the permissions HTMX requires:

<iframe
  sandbox="allow-scripts allow-same-origin allow-forms"
  src="about:blank"
  style="width: 100%; border: none;"
></iframe>
PermissionWhy required
allow-scriptsHTMX needs JavaScript to process hx-* attributes
allow-same-originHTMX needs to make HTTP requests to the agent server (same-origin or CORS)
allow-formsHTMX form submissions (hx-post on <form>)

Explicitly NOT granted:

PermissionWhy denied
allow-top-navigationPrevents the iframe from redirecting the host page
allow-popupsPrevents the iframe from opening new windows
allow-modalsPrevents alert(), confirm(), prompt() from blocking the host
allow-pointer-lockPrevents cursor capture
allow-downloadsPrevents automatic file downloads

Content Security Policy

The host application SHOULD set a CSP on the iframe content to further restrict capabilities:

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:;

Key restrictions:

  • script-src only allows inline scripts and HTMX from a trusted CDN
  • connect-src restricts HTTP/SSE connections to the known agent server
  • No eval(), no external script loading beyond HTMX

Iframe Lifecycle

1. Initialization

The host creates a sandboxed iframe and injects a minimal bootstrap document:

<!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. Streaming

The SSE connection (sse-connect) streams HTML fragments from the agent server. HTMX handles:

  • Progressive rendering as fragments arrive
  • DOM updates via hx-swap strategies (innerHTML, beforeend, outerHTML, etc.)
  • Loading indicators via hx-indicator

3. User Interaction

When a user interacts with generated UI (clicks a button, submits a form), HTMX sends a standard HTTP request to the agent server. The server responds with new HTML fragments that HTMX swaps into the DOM. No postMessage round-trip is needed for agent interactions — this is a direct iframe ↔ server loop.

4. Host Communication

For operations that need to cross the iframe boundary:

// Inside iframe: notify host of a navigation intent
window.parent.postMessage({
  type: 'hg:navigate',
  url: '/dashboard'
}, '*');

// Inside iframe: send data to host
window.parent.postMessage({
  type: 'hg:data',
  payload: { selectedItem: 42 }
}, '*');
// Host: listen for iframe events
iframe.contentWindow.addEventListener('message', (e) => {
  if (e.data.type === 'hg:navigate') {
    router.push(e.data.url);
  }
});

// Host: inject theme into iframe
iframe.contentWindow.postMessage({
  type: 'hg:theme',
  vars: {
    '--hg-surface': '#1a1a2e',
    '--hg-text': '#e0e0e0',
    '--hg-accent': '#7c3aed',
  }
}, '*');

HyperGen vs. MCP Apps Security Comparison

AspectMCP AppsHyperGen
IsolationSandboxed iframeSandboxed iframe (same)
InteractivityJavaScript within iframe (arbitrary)HTMX attributes (declarative) + minimal JS
Agent communicationJSON-RPC over postMessage → host → serverDirect HTTP/SSE from iframe to agent server
StreamingNot supported (full payload delivery)SSE via HTMX (progressive rendering)
Protocol couplingRequires MCPProtocol-agnostic (just HTTP + SSE)
Host bridgeJSON-RPC 2.0 over postMessageSimple postMessage with typed events

The key architectural difference: MCP Apps routes all agent communication through the host (iframe → postMessage → host → MCP server). HyperGen lets the iframe talk directly to the agent server via HTTP/SSE, using postMessage only for host integration (theming, navigation, data passing). This is simpler and enables streaming.


Threat Model

Threats Mitigated by Iframe Sandbox

ThreatMitigation
XSS against hostiframe sandbox prevents access to host DOM, cookies, localStorage
Cookie theftsandbox without allow-same-origin would prevent cookies; with it, cookies are scoped to iframe origin
Host page navigationallow-top-navigation is not granted
Popup spamallow-popups is not granted
Keylogging host inputsiframe cannot access host DOM events

Residual Risks

RiskSeverityMitigation
Malicious agent serverMediumThe agent server URL is configured by the host. Only trusted agent endpoints should be allowed via CSP connect-src.
Exfiltration via HTTPMediumCSP connect-src restricts outbound connections to the configured agent server only.
Iframe phishingLowThe iframe could render content that looks like a host login form. Mitigation: host should visually distinguish iframe regions (subtle border, label).
Resource exhaustionLowMalicious HTML could consume CPU/memory. Mitigation: host can set iframe size limits and destroy iframes that become unresponsive.
allow-same-origin riskMediumWith allow-same-origin, the iframe can access cookies/storage for its origin. If the agent server is same-origin as the host, this broadens access. Mitigation: agent servers SHOULD be on a separate subdomain or use CORS.

Consequences

Positive

  • Strong security by default. The iframe sandbox is a browser-enforced security boundary. No sanitization library required — the browser does the isolation.
  • Streaming works. Unlike MCP Apps, HTMX's SSE extension streams directly into the iframe without postMessage overhead.
  • Simple host integration. postMessage is a well-understood, minimal API. No JSON-RPC complexity.
  • Familiar to the ecosystem. Follows the same pattern as Claude and MCP Apps, making it easy for developers already in that ecosystem to adopt.

Negative

  • iframe rendering overhead. Each HyperGen widget creates a new browsing context. For applications with many small widgets, this could impact performance.
  • CSS isolation is both benefit and cost. The iframe's styles are fully isolated — the host's CSS doesn't leak in, but the host's design system must be explicitly bridged via CSS variables through postMessage.
  • Cross-origin complexity. If the agent server is on a different origin, CORS must be configured. This is standard but adds setup friction.

References

On this page