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
| Channel | Direction | Purpose | Mechanism |
|---|---|---|---|
| Agent → Iframe | Server → Client | Stream UI fragments, respond to interactions | HTTP responses, SSE via HTMX |
| Iframe → Agent | Client → Server | Send user actions (form submit, button click) | HTMX hx-get/hx-post (standard HTTP) |
| Iframe ↔ Host | Bidirectional | Theme injection, resize notifications, navigation, data passing | postMessage 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>| Permission | Why required |
|---|---|
allow-scripts | HTMX needs JavaScript to process hx-* attributes |
allow-same-origin | HTMX needs to make HTTP requests to the agent server (same-origin or CORS) |
allow-forms | HTMX form submissions (hx-post on <form>) |
Explicitly NOT granted:
| Permission | Why denied |
|---|---|
allow-top-navigation | Prevents the iframe from redirecting the host page |
allow-popups | Prevents the iframe from opening new windows |
allow-modals | Prevents alert(), confirm(), prompt() from blocking the host |
allow-pointer-lock | Prevents cursor capture |
allow-downloads | Prevents 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-srconly allows inline scripts and HTMX from a trusted CDNconnect-srcrestricts 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-swapstrategies (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
| Aspect | MCP Apps | HyperGen |
|---|---|---|
| Isolation | Sandboxed iframe | Sandboxed iframe (same) |
| Interactivity | JavaScript within iframe (arbitrary) | HTMX attributes (declarative) + minimal JS |
| Agent communication | JSON-RPC over postMessage → host → server | Direct HTTP/SSE from iframe to agent server |
| Streaming | Not supported (full payload delivery) | SSE via HTMX (progressive rendering) |
| Protocol coupling | Requires MCP | Protocol-agnostic (just HTTP + SSE) |
| Host bridge | JSON-RPC 2.0 over postMessage | Simple 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
| Threat | Mitigation |
|---|---|
| XSS against host | iframe sandbox prevents access to host DOM, cookies, localStorage |
| Cookie theft | sandbox without allow-same-origin would prevent cookies; with it, cookies are scoped to iframe origin |
| Host page navigation | allow-top-navigation is not granted |
| Popup spam | allow-popups is not granted |
| Keylogging host inputs | iframe cannot access host DOM events |
Residual Risks
| Risk | Severity | Mitigation |
|---|---|---|
| Malicious agent server | Medium | The agent server URL is configured by the host. Only trusted agent endpoints should be allowed via CSP connect-src. |
| Exfiltration via HTTP | Medium | CSP connect-src restricts outbound connections to the configured agent server only. |
| Iframe phishing | Low | The iframe could render content that looks like a host login form. Mitigation: host should visually distinguish iframe regions (subtle border, label). |
| Resource exhaustion | Low | Malicious HTML could consume CPU/memory. Mitigation: host can set iframe size limits and destroy iframes that become unresponsive. |
allow-same-origin risk | Medium | With 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
- MCP Apps Specification — The iframe-based approach we build upon
- HTML
sandboxattribute — Browser security primitive - Content Security Policy — Additional restriction layer
- HTMX SSE Extension — Streaming within the iframe
- postMessage API — Iframe-host communication
ADR-003: HTMX as the Interactivity Layer
Why HyperGen uses HTMX attributes for interactivity instead of custom JavaScript frameworks or JSON-based interaction models.
ADR-005: Zero Custom Attributes
HyperGen defines zero custom HTMX attributes. Standard HTMX + SSE extension is sufficient for all Generative UI patterns.