# Client-side template injection

User input compiled as an Angular/Vue template expression instead of bound as data, exposing scope or executing JS

_Area: Web injection and code execution. Seen in 5 disclosed reports; one confirmed bounty: $3,000; latest 2018-08-30. Updated 2026-10-23._

## How it works
A page framework (AngularJS, Vue) treats certain text as a template and evaluates it. Normally user data is bound as a value and shown literally. The assumption that breaks: the app assumes reflected input is inert text, but it is placed where the framework compiles it, so the input becomes an expression the framework executes with access to the page's scope and, in older Angular, a sandbox that can be escaped. The result is script execution or scope data disclosure even when no HTML is injected.

## Where it shows up
- A reflected parameter (search `q`, `s`, discount code) rendered into a page built by Angular/Vue.
- Input passed to template compilation, `$compile`, `innerHTML` on a framework-rendered node, or interpolation contexts.
- Values like avatar/image URIs rendered without `ng-non-bindable` protection [E1](https://bugbountylibrary.com/r/h1%3A271960.md).
- Filters that strip `{{` but not HTML-entity-encoded braces [E3](https://bugbountylibrary.com/r/h1%3A240256.md).

## How to review
Check whether user input reaches a template-compilation sink or is bound as data. The invariant: untrusted input must be inserted as text/value (interpolation binding, `textContent`, `ng-bind`), never compiled as a template. Confirm by sending a harmless expression like `{{7*7}}` and inspecting the rendered DOM (not raw source) for evaluation [E2](https://bugbountylibrary.com/r/h1%3A125027.md). For any reflected parameter on a framework page, verify it is not compiled. Treat `ng-non-bindable` as required on user-controlled attribute values such as URIs [E1](https://bugbountylibrary.com/r/h1%3A271960.md).

## Variants seen
- Search-field input compiled as an Angular expression, exposing the whole page scope; scope traversal reached another user's email, userid and tokens, exfiltrated via a controllable avatar URI [E1](https://bugbountylibrary.com/r/h1%3A271960.md).
- Reflected `q` parameter evaluated by Angular, sandbox escaped to run arbitrary JS and hijack accounts [E2](https://bugbountylibrary.com/r/h1%3A125027.md).
- HTML-entity-encoded braces plus `constructor.constructor` bypassing a prior `{{` filter on search and checkout inputs [E3](https://bugbountylibrary.com/r/h1%3A240256.md).

## Why fixes fail
Filtering literal `{{` is insufficient: entity-encoded braces slip past, and `constructor.constructor` escapes the expression sandbox [E3](https://bugbountylibrary.com/r/h1%3A240256.md). A patch on one endpoint left other sinks (checkout discount code) exposed [E3](https://bugbountylibrary.com/r/h1%3A240256.md).

## Impact and severity
Led to reflected XSS and account hijack [E2](https://bugbountylibrary.com/r/h1%3A125027.md), and cross-user disclosure of email, userid and tokens [E1](https://bugbountylibrary.com/r/h1%3A271960.md). Severity rises when the exposed scope holds secrets or session tokens, when a controllable outbound URL exists for exfiltration [E1](https://bugbountylibrary.com/r/h1%3A271960.md), and when the page is authenticated.

## Evidence
- E1: https://bugbountylibrary.com/r/h1%3A271960.md
- E2: https://bugbountylibrary.com/r/h1%3A125027.md
- E3: https://bugbountylibrary.com/r/h1%3A240256.md
