---
title: "Build generated screens only from your own · Agent Wranglers"
url: https://agentwranglers.se/playbook/app-ui/generated-interfaces/generated-ui-components
id: APP-66
level: Required
applies: ai-features
risk: takeover
updated: 2026-10-22
tokens: 1160
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Build generated screens only from your own components

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Generated interfaces](https://agentwranglers.se/playbook/app-ui/generated-interfaces.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-22.

- ID: APP-66
- Level: Required
- Chapter: [Generated interfaces](https://agentwranglers.se/playbook/app-ui/generated-interfaces.md)
- Rule: When a model shapes part of a screen, it returns data that names components from your own list, checked against a schema. Never insert HTML, script or styles that a model wrote into your page.
- Check: A test feeds the renderer a model answer with `<script>` inside a text prop and an unknown component named `iframe`. The page shows the script text as text and a fallback for the unknown component, and no script runs.
- Risk: An account or system taken over
- Applies when: The project uses AI models.
- How: Text the model read can steer it, so its answer is untrusted, as [Treat model output as untrusted](https://agentwranglers.se/playbook/adversarial-testing/ai-agents-and-models/untrusted-model-output.md) says. HTML from it can run script on your origin. Give the model a schema of allowed components and their props, such as a table, a chart and a form with typed fields. Render an unknown component as plain text. Every button the model adds calls your normal API, so [Enforce permissions on the server](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/server-enforces-permissions.md) still applies. A whole page from a third party, such as an MCP App, loads in a sandboxed frame on another origin. The MCP Apps extension requires that sandbox.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### TypeScript example

```ts
const Block = z.discriminatedUnion('type', [
  z.object({ type: z.literal('text'), body: z.string().max(2_000) }),
  z.object({ type: z.literal('table'), columns: z.array(z.string()).max(8), rows: z.array(z.array(z.string())).max(50) }),
]);

export function renderBlock(raw: unknown): HTMLElement {
  const parsed = Block.safeParse(raw);
  if (!parsed.success) return textBlock('This part could not be shown.');
  return parsed.data.type === 'text' ? textBlock(parsed.data.body) : tableBlock(parsed.data.columns, parsed.data.rows);
}

function textBlock(body: string): HTMLElement {
  const paragraph = document.createElement('p');
  paragraph.textContent = body;
  return paragraph;
}
```

### Python example

```python
class TextBlock(BaseModel):
    type: Literal["text"]
    body: str = Field(max_length=2_000)

class TableBlock(BaseModel):
    type: Literal["table"]
    columns: list[str] = Field(max_length=8)
    rows: list[list[str]] = Field(max_length=50)

Block = TypeAdapter(Annotated[TextBlock | TableBlock, Field(discriminator="type")])

def screen_blocks(model_answer: list[dict]) -> list[dict]:
    blocks = []
    for raw in model_answer:
        try:
            blocks.append(Block.validate_python(raw).model_dump())
        except ValidationError:
            blocks.append({"type": "text", "body": "This part could not be shown."})
    return blocks
```

## Sources

- [OWASP LLM05:2025 Improper Output Handling](https://genai.owasp.org/llmrisk/llm052025-improper-output-handling/)
- [SEP-1865, MCP Apps](https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp)

## Related rules

- SEC-83 [Treat model output as untrusted](https://agentwranglers.se/playbook/adversarial-testing/ai-agents-and-models/untrusted-model-output.md) in Adversarial testing
- APP-46 [Enforce permissions on the server](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/server-enforces-permissions.md) in Web app interface

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/app-ui/generated-interfaces/generated-ui-components">Agent Wranglers web app interface playbook</a>`. In code you write, add a comment next to it with the same link.

- Next: [Let people check and change what a model filled in](https://agentwranglers.se/playbook/app-ui/generated-interfaces/review-generated-input.md)
- [Generated interfaces](https://agentwranglers.se/playbook/app-ui/generated-interfaces.md)
