Rendering Untrusted Content¶
This page is an honest account of kBars's behavior with two different kinds of untrusted input —
untrusted data (a context or @env property whose string values come from users) and untrusted
templates (template source itself written by someone other than the app developer, as in a CMS) —
since the two have different risk profiles and neither is fully covered by "kBars escapes HTML." See
Security and Escaping Notes for what {{ }}/{{{ }}} escaping itself
does and doesn't cover.
Rendering untrusted data¶
Treat context data (JSON/YAML bodies, @env properties, values returned from {{> key ctx }}) the
same way you'd treat any user-supplied string flowing into a Kotlin app: kBars's job ends at HTML
escaping for scalar interpolation (see Security and Escaping Notes). It
does not sanitize for other output contexts — a template whose output is JavaScript, a URL, a shell
command, or SQL needs its own escaping for that context (kBars ships url_escape/url_param_escape
string transforms for the URL case; there is no JS-string or SQL escaping transform, because kBars has
no way to know which target context a given interpolation feeds).
Rendering untrusted templates¶
This is the CMS scenario: end users author the template source itself, not just the data. A few properties of the engine matter here, and a few don't help at all.
Partial recursion is bounded. {{> key }} nesting — including a partial that (directly or through
a chain of other partials) includes itself — is capped by maxPartialDepth, defaulting to 64. Once
exceeded, the over-deep partial renders as nothing and a KbSoftFailEvent.Category.RecursionLimit
soft failure is reported; the render completes rather than recursing without bound. See
Error Handling for observing this via a
KbEventListener, and set a lower maxPartialDepth in KbEnvironmentBuilder if 64 is more headroom
than your partial structure ever legitimately needs.
Parser and expression nesting are not depth-limited. A parenthesized transform-argument chain
((a | b | (c | d))) or deeply nested {{#if}}/{{#each}}/{{#with}} blocks have no engine-imposed
maximum — like most recursive-descent parsers, extremely deep nesting is bounded only by available
call stack, not by an explicit counter. A template source with, say, tens of thousands of nested block
tags is a plausible resource-exhaustion vector if you accept template source from an untrusted party
without a size or nesting-depth check of your own before calling KbTemplate.create.
{{#each}} iterates whatever the context gives it, with no size cap. A template that iterates a
context list has no engine-side limit on how large that list is or how much output it produces — the
usual template-engine amplification risk (a small template producing enormous output against a large
context) applies here as it would with any engine. Bound context size and rendered-output size at the
application layer if you accept either untrusted templates or untrusted (attacker-sized) context data.
A dynamic partial key ({{> (expr) }}) is resolved through your KbPartialLoader. kBars itself
does no filesystem or network access for partials — every partial byte comes from the loader your app
registers via onLoadPartial. If a template can compute an arbitrary string and your loader maps that
string to a file path (or a URL, or a database lookup) without validating it, path traversal or key
confusion is a property of your loader, not of kBars. Validate or allowlist keys in your
KbPartialLoader implementation if the key can originate from untrusted template source or untrusted
context data.
Custom transforms run arbitrary Kotlin code you registered — templates can't add their own. A
transform is a KbTransform your app registers ahead of time; template source can only call a
transform by name and pass it arguments, never define new native behavior. An untrusted template can
choose which of your registered transforms to invoke and with what argument values, so a transform
that does something sensitive (file I/O, an outbound call) based on its arguments is reachable from
template source — write custom transforms with the same suspicion you'd apply to any function whose
arguments might come from an untrusted caller.
Summary¶
| Concern | Bounded by kBars? |
|---|---|
| Partial recursion depth | Yes — maxPartialDepth, default 64 |
| Block/expression nesting depth | No — bounded only by call stack |
{{#each}} output size |
No — bounded only by context size |
| Dynamic partial key resolution | No — your KbPartialLoader's responsibility |
| Custom transform arguments | No — your transform's responsibility |
Where to next¶
- Security and Escaping Notes — what HTML-escaping covers, from the template author's side.
- Error Handling — observing soft failures like
RecursionLimitvia aKbEventListener. - Custom Transforms — registering the only kind of behavior a template can invoke that your app didn't already define.
- Custom Partials —
KbPartialLoaderandmaxPartialDepthconfiguration.