5.2 KiB
title
| title |
|---|
| Security Model |
LiquidJS provides DoS-oriented limits (parseLimit, renderLimit) to reduce risk. This page summarizes those limits, ownPropertyOnly, custom Drop usage, and the security boundary to assume in production.
Security boundary
The built-in limits are cooperative safeguards, not strict runtime isolation.
- They do not equal process RSS/heap usage.
- They do not sandbox JavaScript execution.
- They should be combined with process/container limits and request timeouts for defense in depth.
LiquidJS does not enforce memory or CPU budgets inside the engine. Major template engines take the same approach: byte-level heap tracking is unreliable in garbage-collected runtimes (non-deterministic GC, accounting overhead) and does not map cleanly to real process memory. Jinja2 relies on sys.setrecursionlimit, SandboxedEnvironment for access control, and advises OS/process limits (ulimit, cgroups). Twig documents a SecurityPolicy for tags/filters/methods and explicitly leaves resource limits to PHP (memory_limit, execution timeouts). Handlebars and EJS provide no render budgets; Node.js users typically combine vm.Script timeouts or worker_threads with process isolation for untrusted templates.
For LiquidJS in production, prefer external controls: Node.js vm or worker threads (or packages such as isolated-vm when stronger isolation is required), separate processes or containers, OS/container memory and CPU quotas, and request timeouts — not in-engine heap tracking.
Limits at a glance
- parseLimit: limit total template size per
parse()call. - renderLimit: limit total render time per
render()call.
Limit details
parseLimit
parseLimit restricts the size (character length) of templates parsed in each .parse() call, including referenced partials and layouts. Since LiquidJS parses template strings in near O(n) time, limiting total template length is usually sufficient.
A typical PC handles 1e8 (100M) characters without issues.
renderLimit
Restricting template size alone is insufficient because dynamic loops with large counts can occur during rendering. renderLimit mitigates this by limiting the time consumed by each render() call.
{%- for i in (1..10000000) -%}
order: {{i}}
{%- endfor -%}
Render time is checked on a per-template basis (before rendering each template). In the above example, there are 2 templates in the loop: order: and {{i}}, render time will be checked 10000000x2 times.
renderLimit is not a hard CPU limiter. It is checked between template renders, so compute-intensive filters/tags/user-defined functions or deeply nested template execution between checks can still cause DoS.
Memory-heavy templates (for example exponential concat in a loop) are not capped by LiquidJS. Mitigate them with process/container memory limits, output size checks after render, or template restrictions — the same pattern Jinja2 and Twig recommend for heap and CPU.
ownPropertyOnly and scope data
With ownPropertyOnly true, plain scope objects only expose own properties (no inherited / Object.prototype keys). Default false follows normal JS property access. Use true for untrusted or polluted objects; add strictVariables if missing paths should error. Override per render via RenderOptions. This is a read policy for scope data—not a sandbox for filters, tags, or your code.
Custom Drop classes
Drop values are not restricted the same way: LiquidJS still reads the prototype chain and may call liquidMethodMissing. You control what a drop exposes; narrow APIs and never feed unsafe data into drops unless the class is built for template access. ownPropertyOnly alone does not harden custom drops—audit them like any privileged code.
Online service guidance
If you run an online service, avoid rendering fully user-defined templates whenever possible.
- Prefer curated templates or a restricted template subset.
- If user-defined templates are required, isolate rendering (worker/process/container), enforce OS/container memory and CPU limits, and apply request rate limits.
- Treat
parseLimitandrenderLimitas one layer in a broader DoS defense strategy.
For heavy single-template operations, process-level isolation is still recommended (for example with paralleljs).