# Safety and limits

> Run templates you didn't write.

Kiln is built to run templates written by other people, such as editors in a CMS or AI agents. A template can only read what you give it, and limits stop templates that would run for too long.

## What templates can read

- **Plain objects:** their own properties only. Nothing inherited from `Object.prototype`.
- **Arrays:** items by index, and `length`.
- **Strings:** no properties. Use filters such as `length` instead.
- **Class instances:** only the members their class [exposes](https://kilnlang.com/docs/api/safe-markup/#class-instances).
- **Never:** `constructor`, `prototype` or `__proto__`, even when they are an object's own properties or a `Map`'s keys. Loops and filters skip them too.

Templates can only call functions you registered, the built-ins, macros, their own arrow functions and exposed methods. A function stored in your data can't be called. That includes a `then` method: Kiln never calls one on the data you pass to `render` or on your globals, even while it waits for a promise. A thenable that one of your functions or filters returns, such as a query builder, is awaited like a promise.

## Limits

Every render has limits. Exceeding one stops the render with an error.

| Limit | Default | What it counts |
| --- | --- | --- |
| `maxOutputLength` | 5,000,000 | Characters of output |
| `maxSteps` | 1,000,000 | Tags, outputs, loop passes and calls |
| `maxDepth` | 64 | Nested includes, components, macro calls and layouts |

```ts
const kiln = createKiln({
	loader,
	limits: { maxSteps: 200_000, maxDepth: 32 },
});
```

Leave a limit out, or set it to `undefined`, to keep its default. Each limit must be a number above 0, or `createKiln` throws a `TypeError`.

Built-in filters and functions count the items they process, so one call to `sort`, `merge` or `max` can't do unlimited work. `in` counts the items it searches, and reading a generator or other iterable counts each item it gives. Spreads count the items they copy, and `json`, `urlEncode` and `attrs` count every item they convert. An include or component counts each variable it passes with `with`. Escaping stops as soon as the escaped text is too long to output, and [streamed islands](https://kilnlang.com/docs/tags/island/#streaming-islands) count towards the page's limits.

Pass an `AbortSignal` as the `signal` render option to stop a render from outside, for example when the visitor disconnects. The render stops as soon as the signal aborts, even while it waits for one of your async functions. That function carries on unless it listens to the signal too, which it can read with [`withRender`](https://kilnlang.com/docs/api/extending/#request-data).

The limits count steps, output and nesting, but not the memory a template keeps alive, such as a list holding many long strings. Rely on your platform's memory limits as well. On Cloudflare Workers, the platform's CPU limit is a final backstop.

Your host functions are responsible for bounding concurrent I/O and for timeouts on their own work. Async `map` and `filter` start every callback at once, so use shared pools or queues for calls to databases and APIs. See [Async in filters](https://kilnlang.com/docs/language/async-data/#async-in-filters).

## Other safeguards

- Output is [escaped](https://kilnlang.com/docs/language/escaping/) by default.
- Templates can't build regular expressions, so they can't cause slow pattern matching.
- Templates can't compile other templates from strings.
- Kiln never generates code while rendering. [Precompiled templates](https://kilnlang.com/docs/api/precompiling/) are JavaScript you build and ship, so only precompile templates you trust.
- `dump` outputs nothing unless you turn on `debug`.
- A failed `render` or `renderBlock` returns no partial output. A [stream](https://kilnlang.com/docs/api/streaming/) may already have sent output when it fails.
