Escape
HomeDocsTrust and CSP

Trust and CSP

Intent

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

Report definitions and registered host functions count as trusted configuration. Render data doesn’t.

A definition is something you author, review and version, the same standing as the code that loads it. You name and register host functions yourself, in your own process. Neither arrives from a user. Render data is the opposite, and the engine treats it that way.

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, and @quario/html escapes every interpolated value 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 data value from escaping.

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.

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:. 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.

Literal template text is author-controlled markup. A definition’s own literal text reaches a markup target as markup. That’s a consequence of the boundary rather than an exception to it, and it’s why an untrusted author is outside the model.

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.

report.js
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.

What the boundary doesn’t claim

quario isn’t a sandbox for running definitions you didn’t write. A party who can supply a definition, or register a function, is inside the boundary. No property of the render path puts them back outside it. Accepting definitions from untrusted authors needs a sanitisation policy of your own, separate from anything here.

Where the data claim holds

In HTML, the target escapes every interpolated value, without exception.

In a workbook, no formula is ever written from 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, which is that 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.

Definitions are trusted. Data is not.

npm install quario
Getting started
© 2026 quario · KvK 61815977