Vascula v0.1.5

Safety and limits

An application can render templates written by other people with Vascula. This page says exactly what that covers.

#The model

A template is untrusted code. It can be wrong, careless or hostile.

An application (the Go program embedding Vascula) is trusted. It chooses the data, the permissions, the extensions and the limits.

Data is untrusted text, whoever wrote it.

Given that, Vascula guarantees:

GuaranteeHow
Data cannot inject markupEvery printed value is HTML-escaped. Templates cannot switch it off. See Output.
Data cannot become codeOutput is never evaluated again. A value containing {{ }} prints those characters.
Data cannot choose a script URLA link, source or other URL attribute with printed parts must, as a whole, be relative or use http, https, mailto or tel; anything else prints as #. See Output.
Data cannot add attributesOutput in unquoted values is fully escaped, names cannot be printed, and control flow cannot leave the markup ambiguous. See Output.
A template reads only what it is givenOnly allowed data roots, settings, globals and its own variables. Anything else is an error.
A template uses only registered extensionsUnknown filters fail the render; unknown tags and form kinds fail compilation.
A template cannot exhaust the machineCompilation and rendering are bounded; see below.
A child cannot escape its scopeChildren get only their own permissions and the arguments passed to them.
Mistakes are reported, not hiddenUndeclared names, bad comparisons, division by zero and broken dates are errors, never silent blanks.

#Compilation limits

LimitDefaultOption
Source size1 MiBCompileOptions.MaxSourceBytes
Tokens100,000CompileOptions.MaxTokens
Nesting of blocks and expressions128CompileOptions.MaxNesting

#Rendering limits

These cover the whole render: the template and every child it renders.

LimitDefaultOption
Work (steps)250,000Options.MaxSteps
Output2 MiBOptions.MaxBytes
Size of any one value2 MiB of textOptions.MaxValueBytes
Items in any one value50,000Options.MaxValueNodes
Value nesting64 levelsfixed
Expression depth256fixed
Child render depth32fixed

Work counts what a render does: each tag, expression, filter call, loop iteration and every part of a value a filter processes. A render that would exceed a limit stops with an error that names it:

Template
{% for i in (1..40000) %}{% for j in (1..40000) %}{% endfor %}{% endfor %}
Error
render budget exceeded: too many steps

Values are checked against the size limits when they are used: printed, passed to a filter, compared or serialized. Fields a template never uses are not checked.

Lower the limits only after testing your real templates; raise them only if real templates need it.

#What remains your responsibility

  • Permissions. Allow says which roots a template may read; deciding that correctly is yours. An allowance covers a whole root.
  • Extensions. Filters, tags, the resolver and form handling run as your code, outside the limits. Keep them fast and bounded, and escape anything you put into SafeHTML.
  • Trusted HTML. Only wrap sanitized or self-built markup in SafeHTML, and only fixed attribute markup you build yourself in HTMLAttr.
  • CSS and JavaScript. Escaping does not make a value safe as a script or style, and URLs are only checked in attributes; see Output: what escaping does not cover.
  • Forms. The form tag only writes the token; verify it, and validate every posted value, on the server.
  • Time and memory. The limits bound work, not wall-clock time or process memory. For templates from untrusted authors, render with a timeout, in a process with a memory limit.
  • Error messages. Show them to authors, not to visitors.

#Reporting a security problem

Send a minimal reproducer, the Vascula version and the options involved to developers@tringify.com. Please do not open a public issue for a vulnerability.