Dynamic Frontends
10 min read
Dynamic Frontends let a user generate a complete, custom web application from chat — and then run it safely inside the Unique AI platform. The user describes what they want; a Conduct harness skill scaffolds a full Next.js application from a template; an admin picks that bundle up and turns it into a Dynamic Frontend space. From then on, the generated app is served as an isolated, sandboxed application embedded in chat, talking back to the customer's own data through the Unique API and MCP servers.
It is the same philosophy as UniqueAI Conduct, applied to the frontend: arbitrary, AI-generated application code runs with full flexibility, but inside a hard isolation boundary — no ambient internet, no secrets, no ability to reach anything the customer did not explicitly allow, and no way to act as anyone other than the signed-in user.
This document captures what Dynamic Frontends are for (business view), the architecture, and — most importantly — why they are secure and how a generated app is prevented from escaping its box.
Why Dynamic Frontends?
UniqueAI Conduct can already produce deliverables — reports, spreadsheets, analyses. Dynamic Frontends close the last gap: instead of producing a static artefact, the agent can produce a living application that the whole organisation can open, click through, and interact with.
What it gives you
From prompt to product. A user describes a dashboard or a tool in chat, the harness generates a full Next.js app from a template + a generation skill, and the result is a real, deployable application — not a screenshot or a one-off chart.
Live, interactive, data-backed. Unlike a static report, a Dynamic Frontend reads and writes live data. It can pull from MCP servers (SharePoint, internal systems, your own MCP servers) and the public API, render it, and let users act on it — filter, drill down, submit, trigger workflows.
Dashboards today, generic apps tomorrow. The lead use case is dynamic dashboards built on MCP-server data, but the mechanism is general: any custom internal application a business wants — a request form, an approval queue, a data explorer — can be generated and hosted the same way.
Governed and isolated by construction. Each app runs in its own hardened, VM-isolated runtime with locked-down egress. The customer decides exactly which API endpoints and MCP servers the app may reach. Nothing else is reachable.
No deploy pipeline for the user. The user never touches Kubernetes, CI, or hosting. They generate, save, and an admin attaches the bundle to a space; the platform stands up everything needed to serve it.
The mental model: a generated app in a sealed box, wired to your data through one door
Think of a Dynamic Frontend as a small web application that the agent wrote for you, running in a sealed container that has exactly one door to the outside world — the Unique API — and that door is guarded. The app can be as rich as any web app, but it can only see the data the customer opened that door for, and it can only ever act as the user currently looking at it.
1. End-to-end flow
Generate. In a Conduct space, a user invokes the build-dynamic-frontend skill. The harness scaffolds a complete Next.js application from a fixed template, customised to the user's request.
Save. The generated application is packaged as a
.zipbundle and saved into the Knowledge Base as content (a ZIP). It is identified by acontentIdand asha256hash.Provision. An admin picks that bundle up and creates a Dynamic Frontend space from it (in the Admin app). The space is an Assistant with
uiType = DYNAMIC_FRONTENDand no AI module — the entire runtime is delegated to the generated app.Instantiate. Behind the scenes the platform creates a
ByocAppresource for the bundle. A dedicated controller reconciles it into a running, isolated Kubernetes workload that serves the app over HTTP.Run. When a user opens the space in chat, the generated app is loaded inside a sandboxed iframe, served from a dedicated per-tenant subdomain under a per-app path (
/serve/<app>/). It talks to the Unique API through the two governed paths described in §3.Update. Pointing the space at a newer bundle (a new
contentId/ version) re-deploys the app; sharing and access are left untouched.
1.1 Bundle deployment flow
How a generated app's .zip bundle — identified in the Knowledge Base by its contentId and sha256 — becomes a running, served app. The populator Job materialises the bundle onto a shared volume (PVC); the serve Deployment is launched in parallel, but its copy-bundle init container blocks until the populator finishes, so the app only starts once the bundle is ready.
High-level flow
Detailed sequence — the populator runs first and the Deployment waits for it
2. Architecture
Generation (Conduct harness skill). A template-driven skill produces a self-contained Next.js bundle. Authoring happens inside the governed Conduct sandbox, so even the generation step never has ambient internet or secrets.
Bundle store (Knowledge Base). The
.ziplives as Knowledge Base content, access-controlled like any other file. The runtime fetches it bycontentIdand verifies it against the expectedsha256.ByocAppruntime (BYOC = Bring Your Own Code). Each space maps to aByocAppcustom resource. A controller reconciles it into aDeployment,Service,HTTPRoute,NetworkPolicy/CiliumNetworkPolicy, andPodDisruptionBudget.node-chatis only in the management path (create / update / delete); it is never in the request path of the served app.Dedicated subdomain. Generated apps for a tenant are served from a dedicated per-tenant subdomain (e.g.
byoc.<tenant>.unique.app), deliberately separate fromnext.<tenant>andapi.<tenant>, with each app under its own path (/serve/<app>/). This isolates generated-app browser state from the rest of the product. Sibling apps share that origin, so isolation between apps comes from per-app cookie scoping (name +Path) and the auth proxy's response-header policy, not from separate origins.In-pod auth proxy. Every app pod runs a small trusted proxy in front of the bundle's Next.js (which listens only on loopback). The proxy authenticates the user, enforces access policy, pins the request Origin on writes, and rewrites response headers (CSP, COOP, cookie scoping) before the bundle is ever reached. The bundle cannot disable any of this — it runs in a separate process the bundle can't touch.
Embedding in chat. The chat shell loads the app in a sandboxed iframe (
sandbox="allow-scripts allow-forms allow-same-origin") and polls the runtime status until the app's URL is ready (the “Preparing dashboard…” state).
3. How a generated app talks to Unique (the only two doors)
A Dynamic Frontend has no general-purpose network access. It can reach the Unique platform through exactly two governed channels, and nothing else.
3.1 Public API (locked-down, server-side egress)
The app's own server code can call the Unique public API (https://api.<tenant>.unique.app/...). All pod egress is funnelled through the egress gateway (sbx-gateway):
The pod has no ambient outbound network. A
NetworkPolicypermits egress only to the gateway and cluster DNS — the app cannot open a raw TCP/TLS connection to the internet or to any other in-cluster service.The gateway TLS-intercepts outbound calls and enforces a strict upstream allow-list. The app can only reach the destinations the customer has explicitly allowed. Everything else is dropped.
Identity is enforced via a hairpin through Kong: the user's
Authorizationbearer is forwarded, Kong re-validates the JWT against Zitadel and overwrites the identity headers from the verified claims. The app therefore acts strictly as the calling user and cannot impersonate another user or tenant.
3.2 Host bridge (iframe ↔ shell, for MCP calls)
For data that comes from MCP servers, the app talks to the chat shell over a postMessage host bridge rather than calling out itself:
The iframe posts a
unique:callMcpToolmessage (MCP server id + tool name + args) to the parent window.The chat shell validates the message origin and source, then forwards it to
node-chat'smcpCallToolGraphQL mutation with the signed-in user's token, and posts the result back to the iframe.The app never holds credentials for MCP servers and never connects to them directly — the shell brokers every call under the user's identity and access rights. If the user isn't connected to the required MCP server, the bridge surfaces a “Connect” prompt.
This is what makes “dashboards over MCP data” work: the generated app renders and interacts with MCP-backed data without ever being trusted with the connection itself.
4. Security model — why it cannot escape its box
Dynamic Frontends run untrusted, AI-generated code, so the isolation boundary is the whole point. Several independent layers each have to fail for an app to misbehave.
4.1 No ambient internet — “no SSL connection per se”
A generated app cannot open arbitrary outbound HTTPS/TLS connections. There is no ambient egress: the only outbound path is the egress gateway, and the gateway only forwards to an explicit allow-list. Even a bundle that ignores HTTP(S)_PROXY is stopped at the NetworkPolicy / Cilium layer (on Cilium, L7 DNS rules also prevent DNS-tunnel exfiltration). So the app has no general connection to the outside world — it can only reach the customer-approved Unique endpoints, and only through the guarded door.
4.2 Tenant isolation — enforcement points
L0 — Kong. Without a valid token, requests never reach the app's data plane; identity comes from the token, not the client.
L1 — HTTPRoute. The route matches on the app's path prefix (
/serve/<app>/) on the dedicated host and rewrites it to/; itsbackendRefpoints at the currently active version's Service. (An earlier revision also matchedx-company-idon the route, but Kong matches routes before its plugins run, so a plugin-stamped header cannot be used as a match key — tenant enforcement therefore lives in the in-pod auth proxy, below.)L2 — in-pod auth proxy. Application-layer policy: the app is tied to a specific space/company, and the proxy verifies the caller's identity and company-id (
BYOC_ACCESS_COMPANY_ID) plus USE access againstnode-chaton every inbound request (TTL-cached). The proxy — not the route — is the source of truth for authenticating/serve/*requests, reading the__Secure-byoc-session-<app>cookie set byPOST /_launch.
4.3 The bundle cannot forge identity or reach other services
On the egress hairpin: the gateway strips every identity-shaped header (x-user-id, x-company-id, x-user-roles, x-service-id, cookies) and only whitelists Authorization for the public-API host; Kong then re-injects identity from the validated JWT. The bundle can only ever possess the calling user's own token. Direct connections to in-cluster services like node-chat are dropped by the NetworkPolicy. Net effect: an app can act as the current user against allow-listed endpoints — and nothing more.
4.4 No secrets in the app
The generated app never holds API keys or MCP credentials. Public-API calls reuse the user's forwarded JWT; MCP calls are brokered by the shell under the user's identity. There is nothing to steal inside the bundle.
4.5 Browser-origin hardening
Serving on a dedicated subdomain isolates generated-app browser state from the rest of the product. The in-pod auth proxy sets frame-ancestors to the allowed chat frontend origins (clickjacking / cross-app framing protection; 'none' when none configured), forces Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp and Cross-Origin-Resource-Policy: same-origin, a connect-src 'self' CSP (so the app's browser code can't fetch() an attacker host), strips Service-Worker-Allowed, and scopes session cookies per app (name + Path). The chat iframe is sandboxed and pins postMessage source/origin on the bridge.
4.6 Pod hardening
App pods run under VM-level isolation (kata-vm-isolation by default; the RuntimeClass is configurable per cluster), as non-root (65532), with allowPrivilegeEscalation=false, all capabilities dropped, a read-only root filesystem (writes go to emptyDir), and seccomp: RuntimeDefault.
5. Glossary
Term | Meaning |
|---|---|
Dynamic Frontend | A generated web application (Next.js) run as an isolated, embedded app inside a Unique space. |
Dynamic Frontend space | An Assistant with |
build-dynamic-frontend skill | The Conduct harness skill that scaffolds a Next.js bundle from a template. |
Bundle | The generated app packaged as a |
ByocApp | The Kubernetes custom resource that represents a deployed bundle; reconciled by the byoc-controller into the running workload. |
Egress gateway (sbx-gateway) | The mandatory, TLS-intercepting, allow-listed egress path; the app's only route to the outside. |
Auth proxy | The trusted in-pod process in front of the bundle that enforces auth, access policy, Origin pinning, and response-header hardening. |
Host bridge | The |
Hairpin | Routing the app's public-API call back out to the LoadBalancer and in through Kong, so the JWT is re-validated and identity headers are re-derived. |