# Template sandbox escape via property access

Template engine deny lists bypassed by own-property lookup, raw config arrays, or untyped AST fields

_Area: Web injection and code execution. Seen in 3 disclosed reports; latest 2026-10-08. Updated 2026-10-24._

## How it works
Template engines try to keep untrusted templates from reaching dangerous values by deny-listing names (`constructor`, `config` paths) or by validating AST node types. The assumption that breaks: the guard covers only the shapes the author imagined. A deny list checked *after* an own-property short-circuit never runs for names that are own properties of prototypes. A sandbox that wraps one variable leaves sibling raw arrays readable. A validator that type-checks three node kinds leaves every other field the code generator interpolates unchecked. In each case the engine trusts a value the guard never inspected.

## Where it shows up
- Property lookup that returns `hasOwnProperty` results before the allow/deny check runs [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-p8wg-vrv2-v86f.md)
- Sandbox policies that gate object method/property access but not array key reads [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-p597-crqc-m349.md)
- A fix that wraps one context variable (e.g. `config`) while `system`/`site`/`theme` are copied verbatim [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-p597-crqc-m349.md)
- AST validators that whitelist node types and skip plain objects without `type` or non-object values [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-8r5x-fm3f-whwj.md)
- Compiler code that interpolates AST fields (e.g. `Program.blockParams.length`) raw into generated JS [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-8r5x-fm3f-whwj.md)
- Template variables carrying full config trees, request data, or secrets into a sandboxed render [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-p597-crqc-m349.md)

## How to review
The invariant: every value reachable from a template must pass the same guard before use, regardless of how it is reached. Concretely:
- For property access, confirm the deny/allow check runs *before* any own-property or prototype short-circuit, and that prototype members like `constructor` are covered [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-p8wg-vrv2-v86f.md).
- For sandboxed renders, enumerate every variable in the render context and confirm each is filtered, not just the one the last fix touched [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-p597-crqc-m349.md).
- For AST validation, list every field the code generator interpolates and confirm each lives on a node the validator actually visits; reject untyped objects and non-object values [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-8r5x-fm3f-whwj.md).
- Trace from template input to the sink (generated JS, config read, function call) and check the guard sits on that path.

## Variants seen
- Own-property short-circuit bypassing a `constructor` deny list, reaching `Function.prototype.constructor` [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-p8wg-vrv2-v86f.md)
- Raw config arrays (`system`, `site`, `theme`) copied into a sandboxed page render, leaking secrets via array key reads [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-p597-crqc-m349.md)
- AST type confusion: untyped field interpolated raw into generated JS, giving RCE through `compile()` [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-8r5x-fm3f-whwj.md)

## Why fixes fail
Each fix addressed the reported shape only. The 4.7.9 AST validator checked three node types and missed other interpolated fields [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-8r5x-fm3f-whwj.md). The earlier Grav fix wrapped `config` but left sibling variables raw [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-p597-crqc-m349.md). The deny list existed but ran after the own-property check [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-p8wg-vrv2-v86f.md).

## Impact and severity
Critical: arbitrary JavaScript execution via `compile()` [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-8r5x-fm3f-whwj.md), RCE via prototype `constructor` [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-p8wg-vrv2-v86f.md), and secret disclosure from config trees [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-p597-crqc-m349.md). Severity rises when the engine is reachable from user-supplied templates or page content, and when fixes are partial, leaving sibling paths open.

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-8r5x-fm3f-whwj.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-p597-crqc-m349.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-p8wg-vrv2-v86f.md
