---
title: "Trust and CSP — quario"
description: "The quario trust boundary, rendering under a script policy without unsafe-eval, and bounding what a render may read."
url: "https://getquario.com/docs/diving-deeper/security"
---

# Trust and CSP

## Intent

One claim decides how quario fits a security review, and it’s worth stating
before anything else.

> **quario trusts the host’s configuration. It trusts neither the report
definition nor its render data.**

The host is the code that creates the instance and gives it a report. quario
trusts what it configures, because that lives in your own process. It covers
registered functions and the query budgets. It also covers the scheme
allowlist, fonts, and the license key. A definition is the opposite. An agent
may have written it, or a user through an embedded editor. So the engine treats
it as untrusted input, the same as render data. Nothing a definition says
executes, and nothing it says becomes markup.

Render data arrives already resolved, because the engine opens no connection of
its own. The one place it waits for your code is an [asynchronous registered
function](https://getquario.com/docs/diving-deeper/host-functions.md), which is host code you
registered. [Supplying the data](https://getquario.com/docs/diving-deeper/supplying-data.md) describes
where the data comes from.

## Design decisions

**Nothing turns a string into JavaScript.** Expressions parse into closures
when a definition compiles. There is no `eval`, no `new Function`, and no code
generation anywhere in the render path, which is why a page can ship a script
policy that omits `unsafe-eval`. The engine’s suites run under Node’s
`--disallow-code-generation-from-strings`, a source scan, and browsers
loading the published files under a strict policy.

**Escaping belongs to the target, at its own edge.** The event stream carries
unescaped values. `@quario/html` escapes every character a definition or its
data contributes: literal template text, header cells and interpolated values,
and every generated attribute. A consumer that embeds stream values in markup
owns its own escaping.

**There is no unescaped form.** `{{{ }}}` is a definition error rather than an
escape hatch, so no schema syntax can exempt a value from escaping.

**Formatting arrives only as declarations.** Markup in a definition’s own text
makes no element in any target. Emphasis comes from `style` and styled runs,
which a target turns into attributes it escapes.

**A location is the one value the engine judges.** Every other declaration
reaches the target as the author wrote it, because a bad-looking render is a
recoverable fault. A URL isn’t: `javascript:` is a string-to-code path, and
escaping at the markup edge can’t answer it. So the engine admits an `href`
before it crosses, against an allowlist the host owns. What no target ever
receives is a string it has to decide about.

**A report reads your data and can’t assign to it.** Expressions also inherit
the expression engine’s guards, so `__proto__`, `constructor` and `prototype`
never resolve, and a function comes only from the registry you passed. An
expression has no loops and can’t call itself, so a definition can’t spin a
render forever either.

**The no-string-to-code guarantee covers script execution, not every
directive.** Conditional formatting emits inline `style` attributes, so a
restrictive `style-src` has to account for them. An image item’s `data:` URI
needs `img-src data:`. The viewer and the editor paint an SVG image from a
`data:` URI as well, so a page that previews one needs the same grant. A PNG or
JPEG there needs none. A grant for content you asked for is an ordinary
deployment requirement, not a hole.

**The unlicensed marking never depends on a grant.** A blocked marking would
fail open, so it’s the one output that must render without one.

## API walkthrough

### Bounding what a render may read

The JSONPath selection is the one place a hostile dataset can cost you time
rather than correctness. Budgets are an API argument, never a schema field, so
a definition can’t raise its own ceiling.

```ts title="report.ts"
import { quario } from 'quario'

const q = quario({ query: { maxNodes: 10_000, maxDepth: 64, maxResults: 1_000 } })
```

**`maxNodes`** bounds how many nodes the traversal visits. **`maxDepth`** bounds
how deep it goes. **`maxResults`** bounds how many rows it may select.

A budget failure points at `data`, keeps its `RangeError` type, and carries
`code`, `limit` and `actual`. Every render starts with fresh counters, so one
expensive dataset can’t exhaust the next.

`quario()` checks the budgets when you call it, so a bad value fails at startup
even for a report that never runs a query.

### Admitting the URLs a document may link to

**`quario({ schemes })`** is that allowlist, defaulting to `https:` and
`mailto:`. Each entry is lower-case and ends in a colon. A value the engine
can’t read as an allowlist, `null` included, throws a `TypeError` at
`quario()`, prefixed `options.schemes:`.

```ts title="report.ts"
import { quario } from 'quario'

const q = quario({ locale: 'en-IE', schemes: ['https:'] })
```

A refused value drops out, so the text renders and the link doesn’t. A URL
arrives from data, and failing the whole render the day a row changed would
break a report that was working. A URL stating no scheme falls with the rest. A
scheme list can’t say “no scheme” without also admitting `//host/path`, which
states none and leaves the document all the same.

The allowlist is the host’s, never the document’s. A report that could widen
it would be author data authorising itself. A `schemes` value the engine can’t
read throws at construction rather than using the default. An allowlist quietly
replaced means a host believes it restricted something it didn’t.

An `href` beginning `#` is an internal target naming a group’s `label`. It
leaves the document by no scheme, so no allowlist reads it.

### What the boundary doesn’t claim

A registered function is host code, and quario doesn’t limit what it does once
the engine calls it. Whoever registers one is inside the boundary, so register only functions
you would run on any definition.

quario guarantees that nothing a definition says executes. It doesn’t guarantee
that the report is right. A definition an agent wrote can still read the wrong
field or total the wrong column, which is what validation and review are for.

### Where the claim holds

**In HTML**, the target escapes every character a definition or its data
contributes, without exception.

**In a workbook**, the target writes no formula from a definition or a data
value, so a cell holding `=SUM(A1:A9)` is a string that says so.

**In CSV**, the target mangles formula-like text on write, whichever of the two
it came from. That’s this format’s version of the same rule.

### Environments

The source and the builds are runtime-neutral JavaScript. Node.js LTS and
Chromium under a strict Content Security Policy are the tested environments.
Every package is ESM-only.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://getquario.com/"},{"@type":"ListItem","position":2,"name":"Docs","item":"https://getquario.com/docs"},{"@type":"ListItem","position":3,"name":"Trust and CSP","item":"https://getquario.com/docs/diving-deeper/security"}]}
```
