# Security Nunjitsu is designed to render fully untrusted template source without giving that source implicit access to JavaScript or the surrounding Node.js process. It does this with a closed interpreter and an explicit capability model. This page describes the security contract applications can rely on. Exact implementation invariants and regression details live in [`CONTEXT.md`](../CONTEXT.md). ## Threat model Assume an attacker controls the complete template source and every value passed through an untrusted context. The attacker may deliberately trigger unusual syntax, coercion, recursion, large outputs, errors, and capability calls. The renderer is responsible for preventing that input from: - executing generated or dynamically constructed JavaScript; - reaching host globals, prototypes, constructors, getters, methods, or module loading; - turning template data into a callable application capability; - passing internal runtime objects or callable handles to application code; or - escaping through exception objects or diagnostic formatting. ## What the boundary guarantees Templates execute only through parser and interpreter operations implemented by Nunjitsu. The runtime does not use `eval`, `Function`, `node:vm`, generated JavaScript, or template-controlled dynamic imports. Every context and capability result is recursively copied into renderer-owned values. Scopes and records are private and map-backed, and all lookup, coercion, comparison, iteration, and call behavior is implemented by closed value kind. The names `constructor`, `prototype`, and `__proto__` are reserved throughout the language and data boundary. Only sealed identities created for macros, built-ins, approved intrinsic methods, and registered filters or globals are callable. Intrinsics use a fixed receiver-specific allowlist and never expose a host method or prototype. Context functions, arbitrary object paths, and computed property names cannot manufacture authority. ## Accepted data The public value model accepts: - `null`, booleans, numbers, and strings; - arrays containing accepted values; - plain records containing accepted values in enumerable own data properties; - exact native Maps with accepted keys and values; and - exact native Sets with accepted values. The copier rejects functions, symbols, accessors, proxies, custom prototypes, Map and Set subclasses, class instances, typed arrays, dates, promises, errors, cycles, and other behavior-bearing values. It inspects property descriptors rather than reading getters. Maps and Sets are snapshotted through captured intrinsics without consuming an object-owned iteration protocol. Prepared contexts contain the same copied values. They never observe later mutation of the caller's objects, and failed path updates do not modify the original snapshot. Safe-value copying has non-configurable structural ceilings: one copied graph may contain at most 100,000 array slots, record properties, Map entries, and Set values across at most 256 nested container levels. Each individual container has the same 100,000-entry ceiling, and a prepared-context update path may contain at most 256 segments. Sparse arrays are charged by their logical length before their elements are inspected. `renderValue` returns values through the same boundary. Arrays and records are fresh frozen public copies; Maps and Sets are fresh detached native containers; safe strings become ordinary strings; regular expressions become inert strings; and callable identities are rejected. Approved array, Map, and Set mutations operate only on the current render's copy. Aliases within that render observe the mutation, but caller-owned values, prepared contexts, configured globals, later renders, and failed renders do not. ## Capabilities Filters and global functions are trusted application code. Registering one grants every template rendered by that renderer permission to invoke it with attacker-controlled arguments. Keep capabilities narrow: - validate arguments for the application operation they perform; - avoid generic object merging, property access, command execution, or query construction; - return only supported plain data; - do not return secrets that the template should not render; and - treat a capability error message as potentially sensitive application data. Arguments are detached public copies with no internal runtime objects or callable handles. Arrays and records are frozen; Map and Set arguments are independent native snapshots. Results cross the same safe-value copier before evaluation continues. If a callback throws or returns an invalid value, rendering stops immediately. Capability callbacks run in the caller process and are outside evaluator work accounting. They must be synchronous, bounded, and safe for the application to execute. ## Resource limits Every render starts with cooperative limits: | Limit | Default | Covers | | ----------------- | -----------: | ------------------------------------------------ | | `sourceCodeUnits` | `4_194_304` | UTF-16 source length | | `astNodes` | `1_000_000` | Parsed AST nodes | | `workUnits` | `1_000_000` | Static planning, evaluation, and value expansion | | `nestingDepth` | `512` | Nested statement and expression evaluation | | `outputCodeUnits` | `16_777_216` | Rendered text or equivalent native-result output | | `scratchBytes` | `67_108_864` | Estimated data supplied to one filter | | `capabilityCalls` | `4_096` | Registered filter and global calls | Applications can override individual values through `TemplateRenderOptions.limits`. Each override must be a non-negative safe integer or `Infinity`. The structural safe-value ceilings described under [Accepted data](#accepted-data) are separate hard invariants and cannot be disabled through render options. Every regular-expression pattern is likewise capped at 16,384 UTF-16 code units before native syntax validation, regardless of whether regex execution is enabled. These checks are availability safeguards, not a hard memory limit, exact CPU budget, or process sandbox. Use process isolation when the deployment requires strong resource containment or protection from bugs in trusted capabilities. ## Regular-expression execution Regular-expression literals are inert by default. They can be rendered, compared, stored, or passed to a capability as canonical strings, but the built-in `replace` filter and string `replace` and `split` methods reject them as patterns, and the regex `test` method cannot execute. Ordinary string replacement remains available. Applications can opt into Nunjucks-compatible regular-expression execution for a renderer: ```ts const renderer = createTemplateRenderer({ allowRegexExecution: true, }); ``` This renderer-wide setting enables regex patterns in the built-in `replace` filter and string `replace` and `split` methods, plus stateful regex `test`. These operations use Node.js's native regular-expression engine, including captures and flags. The setting does not affect a custom registered filter named `replace`, which is trusted capability code like any other registered filter. A hostile enabled pattern and input can cause excessive backtracking and block the Node.js event loop. Render work limits do not close this gap. Once a synchronous native regular-expression operation begins, the interpreter cannot account for or interrupt its internal matching work. Protection from regular-expression denial of service is therefore outside Nunjitsu's current availability guarantees. Deployments that require a hard execution boundary must render in a separately isolated process that can be terminated. A future version may use a linear-time regular-expression engine or define a smaller executable pattern language. A heuristic scan for known problematic patterns would be useful defense in depth, but would not be treated as a security guarantee. ## Failures and diagnostics API validation errors are reported before template evaluation. Parser and runtime failures use `TemplateRenderError`; exhausted resource limits use `TemplateLimitError`. Both rendering methods are fail-stop and never return partial output. Public render errors contain renderer-owned, bounded, single-line diagnostics and safe template coordinates when available. They do not retain the internal exception or a capability-thrown object as `cause`. Applications should still avoid returning diagnostics directly to untrusted clients because a trusted capability may include sensitive information in its own error message. ## Regular-expression state JavaScript regular-expression operations can modify deprecated process-global fields such as `RegExp.$1`. Nunjitsu clears that legacy state before and after every capability and again when a render exits. Rendering may therefore clear legacy RegExp state that application code set earlier; it cannot restore that state reliably. Applications and capabilities must not rely on those deprecated fields. The `random` filter uses Node.js cryptographic integer selection and does not read or advance the application's `Math.random` stream. ## Output is still untrusted The interpreter prevents templates from gaining JavaScript authority; it does not make their rendered text safe for a destination. Automatic escaping is disabled. Applications must apply the correct sink-specific handling for HTML, URLs, SQL, shells, configuration files, and other consumers. ## Outside the guarantee Nunjitsu does not provide hard process isolation, exact CPU or heap accounting, safe behavior for application capabilities, protection from the opt-in native regular-expression backtracking described above, secret-data zeroization, or sanitization of rendered output. The security contract also assumes trusted standard Node.js intrinsics and an uncompromised Nunjitsu installation.