Client-Side Template Injection: Finding CSTI Beyond Alert Boxes
You submit {{7*7}} and the page prints 49. That tiny result tells you something useful: your input was inserted into a client-side template and evaluated as code-like data. It does not automatically mean full XSS, and that distinction saves a lot of rejected reports.
Client-Side Template Injection shows up most often in older AngularJS applications, embedded admin panels, and hybrid pages where server-rendered data is compiled again in the browser.
Find the compilation boundary
Search the HTML and JavaScript for clues: ng-app, ng-bind, $compile, Vue delimiters, Handlebars helpers, or a custom template renderer. Then locate inputs reflected inside the framework-controlled DOM. Search pages, profile names, support tickets, and preview features are productive.
Start with arithmetic probes:
{{7*7}}49for engines using dollar expressions- A harmless string method supported by the suspected framework
Compare page source with the live DOM. If the raw braces exist in the HTTP response but become 49 only after JavaScript runs, the browser template engine did the evaluation. That is stronger evidence than a reflected string transformed server-side.
Context decides the escape
An expression in plain text behaves differently from one inside an attribute or an already-sandboxed component. Identify the exact framework version from bundles or runtime objects before copying a payload from a cheat sheet. AngularJS sandbox escapes are sharply version-specific; random payload spraying creates noise and can trigger defenses without teaching you anything.
Try to prove access to benign application state first. Can the expression read a public scope value, call a safe string function, or alter a canary element? If the Content Security Policy blocks inline script, template evaluation may still execute framework expressions because trusted framework code performs the work. That can make CSTI a route around a strict-looking CSP, but only claim it when your browser proof succeeds.
Stored CSTI deserves special attention. Put a test expression in your own profile name, then view it from a second account or an admin preview you are allowed to access. Execution in another user's session changes the story from self-XSS to stored cross-user code execution.
Don't stop at alert(1)
A safe bounty proof can write a random marker into the DOM or send a request to an endpoint you own if policy allows out-of-band callbacks. Show which origin and session execute it. Do not read another user's data.
Severity depends on reach. An expression accepted only in a local theme editor is low. A support-ticket subject compiled in an agent dashboard may expose privileged sessions. Explain that delivery path, required interaction, and affected role.
Fix the rendering path
Escaping braces with a blacklist is brittle because template languages offer many equivalent expressions. The durable fix is to place untrusted text through a non-compiling sink such as textContent, avoid calling $compile on user-influenced HTML, and upgrade retired frameworks. A CSP is useful containment, not a cure for data being treated as a template.
In your report, include the original response, the DOM after compilation, framework version, payload, and a short screen recording. Triage should be able to see the browser turn data into an expression without reverse-engineering your setup.