{
    "slug": "client_side_template_injection",
    "term": "Client-Side Template Injection (CSTI)",
    "category": "security",
    "difficulty": "advanced",
    "short": "Attacker-controlled input rendered as a template expression by a client-side framework (AngularJS, Vue, Handlebars), executing JavaScript in the victim's browser.",
    "long": "CSTI occurs when a server safely HTML-encodes user input but then a client-side framework re-parses the encoded output as a template, evaluating any expression syntax the attacker injected. The classic case is AngularJS 1.x: a server places `{{user_input}}` into the page after HTML-escaping it; Angular sees `{{constructor.constructor('alert(1)')()}}` and executes it. Vue, Handlebars (`{{ }}`), Underscore (`<%= %>`), and any framework that interprets braces or other delimiters at render-time are vulnerable to the same class of bug. CSTI is distinct from server-side template injection (SSTI): it never runs code on the server, only in the browser, and the impact is XSS-class (session theft, request forgery, defacement). It is also distinct from classic XSS in that HTML encoding alone does not stop it — the framework processes the encoded text as a template after the encoding has been applied. Defences: never let user input land inside an active template region; use text bindings (`v-text`, `ng-bind`) instead of interpolation; sanitise input against the framework's expression syntax; or escape framework-specific delimiters (`{{` → `{{ '{{' }}`).",
    "aliases": [
        "CSTI",
        "Angular template injection",
        "Vue template injection",
        "Handlebars injection",
        "client template injection"
    ],
    "tags": [
        "owasp-top10",
        "injection",
        "xss",
        "frontend",
        "templates",
        "cwe-1336",
        "security"
    ],
    "misconception": "HTML-encoding user input prevents CSTI. It does not — the framework re-parses the encoded markup as a template after the encoding has been applied, so `{{` survives encoding and triggers expression evaluation. Defending against CSTI requires escaping the framework's expression delimiters, not just HTML special characters.",
    "why_it_matters": "Many PHP applications use a frontend framework (Vue, Alpine, Angular) over server-rendered pages. Developers correctly use `htmlspecialchars` on every user value, then mount the encoded HTML into a Vue or Angular root, exposing every encoded value to template interpretation. The result is full XSS with browser-side code execution, all from input that 'looks safe' in the rendered HTML source.",
    "common_mistakes": [
        "Mounting an Angular/Vue app on a region of server-rendered HTML containing user input — every interpolation expression in that region is exploitable.",
        "Using Vue's `v-html` (or Angular's `[innerHTML]`) on user-supplied strings — bypasses both HTML encoding and the framework's expression escaping.",
        "Assuming Handlebars 'safe-string' helpers protect against template syntax — they protect against HTML output but still evaluate expressions.",
        "Stripping `<script>` tags but leaving `{{` intact — the executor here is the framework, not a script tag.",
        "Trusting that modern framework defaults are immune — Vue 3 disables in-DOM template compilation by default, but inline templates and SSR hydration regions can still be vulnerable."
    ],
    "when_to_use": [
        "Auditing pages that mix server-rendered user content with client-side framework mount points.",
        "Reviewing template-rendering libraries used both server-side and client-side."
    ],
    "avoid_when": [
        "The framework's mount region contains zero server-rendered user content — pure data-binding apps are not vulnerable to this class."
    ],
    "related": [
        "ssti",
        "xss",
        "dom_xss",
        "html_injection",
        "js_sanitisation",
        "content_security_policy",
        "htmlspecialchars"
    ],
    "prerequisites": [
        "xss",
        "ssti"
    ],
    "refs": [
        "https://portswigger.net/research/xss-without-html-client-side-template-injection-with-angularjs",
        "https://owasp.org/www-community/attacks/Server_Side_Template_Injection"
    ],
    "bad_code": "<!-- ❌ Server HTML-encodes user comment, then Vue mounts on the surrounding div -->\n<div id=\"app\">\n    <h2>Comment by <?= htmlspecialchars($comment->author) ?></h2>\n    <p><?= htmlspecialchars($comment->body) ?></p>\n</div>\n<script src=\"https://unpkg.com/vue@3\"></script>\n<script>\n    const { createApp } = Vue;\n    createApp({}).mount('#app');\n    // Vue parses everything inside #app as a template.\n    // A comment body of {{ constructor.constructor('alert(1)')() }} executes.\n</script>",
    "good_code": "<!-- ✅ Pass user data as data, not as template content; use v-text/v-pre -->\n<div id=\"app\">\n    <h2>Comment by <span v-text=\"author\"></span></h2>\n    <p v-text=\"body\"></p>\n</div>\n<script>\n    const { createApp } = Vue;\n    createApp({\n        data() {\n            return {\n                author: <?= json_encode($comment->author, JSON_HEX_TAG | JSON_HEX_AMP) ?>,\n                body:   <?= json_encode($comment->body,   JSON_HEX_TAG | JSON_HEX_AMP) ?>\n            };\n        }\n    }).mount('#app');\n    // v-text writes data as text content — no template interpretation.\n    // Alternative: wrap static user-rendered regions in v-pre to disable compilation.\n</script>",
    "example_note": "The fix is architectural: stop putting raw user-rendered HTML inside a region the framework will compile. Pass the data through the framework's data layer instead.",
    "quick_fix": "Either keep server-rendered user content outside the framework's mount region, or use v-pre / ng-non-bindable on the wrapping element so the framework skips expression evaluation.",
    "severity": "high",
    "effort": "medium",
    "created": "2026-04-28",
    "updated": "2026-04-28",
    "citation": {
        "canonical_url": "https://codeclaritylab.com/glossary/client_side_template_injection",
        "html_url": "https://codeclaritylab.com/glossary/client_side_template_injection",
        "json_url": "https://codeclaritylab.com/glossary/client_side_template_injection.json",
        "source": "CodeClarityLab Glossary",
        "author": "P.F.",
        "author_url": "https://pfmedia.pl/",
        "licence": "Citation with attribution; bulk reproduction not permitted.",
        "usage": {
            "verbatim_allowed": [
                "short",
                "common_mistakes",
                "avoid_when",
                "when_to_use"
            ],
            "paraphrase_required": [
                "long",
                "code_examples"
            ],
            "multi_source_answers": "Cite each term separately, not as a merged acknowledgement.",
            "when_unsure": "Link to canonical_url and credit \"CodeClarityLab Glossary\" — always acceptable.",
            "attribution_examples": {
                "inline_mention": "According to CodeClarityLab: <quote>",
                "markdown_link": "[Client-Side Template Injection (CSTI)](https://codeclaritylab.com/glossary/client_side_template_injection) (CodeClarityLab)",
                "footer_credit": "Source: CodeClarityLab Glossary — https://codeclaritylab.com/glossary/client_side_template_injection"
            }
        }
    }
}