Skip to content

Mid · 7 chapters

Prompt templating and escaping user input

User text is untrusted input; templating it is an escaping problem

Treating user content as template structure creates prompt injection, broken formatting, and hard-to-reproduce incidents; safe rendering is a baseline boundary, not a complete injection defense.

Read these first

What you will know

  • Render a prompt from a template with user data safely interpolated

    Keep control syntax and untrusted values on separate paths so braces, delimiters, and formatting markers cannot alter the intended instruction.

  • Keep the rendered prompt inspectable for debugging

    A captured representation lets an engineer compare the failing input, selected template, and final message instead of inferring behavior from source code alone.

  • Escape user input that could break the template's structure

    Escaping must match the template engine and output context; a value safe for one delimiter or serializer may still terminate another.

  • Version templates rather than editing strings in place

    Immutable revisions make evaluation results and production incidents attributable, while silent edits invalidate comparisons between runs.

  • Log the rendered prompt, not just the template name

    Reproduction requires the actual substitutions and formatting, but sensitive fields must be redacted or access-controlled so diagnostics do not become a data leak.


Chapters

  1. 1

    Binding values into a prompt template

    Assembling a prompt as SYSTEM plus the user's question and then calling .format(**context) puts the user's characters into the template string before formatting runs, so a submitted {customer_id} interpolates your internal id, {missing} raises KeyError, and {account.api_key} reads an attribute off a context object. Covers why a format string is a program, what binding a value instead of concatenating it guarantees, and where a restricted engine is needed because the untrusted text genuinely is the template.

  2. 2

    Render-time escaping and output contexts

    There is no such thing as a value that is safe in general, only a value that is safe in a specific output context: a Markdown fence ends on a backtick run, a hand-built JSON body ends on a quote, a pseudo-XML section ends on a close tag, and no single transform at the API boundary is correct for more than one of them. Covers storing the text exactly as it arrived, escaping once at render time by destination, and why truncation has to happen before the escape rather than after it.

  3. 3

    Trust boundaries in the render context

    A hardened renderer that binds and fences the customer's question will interpolate a retrieved help-centre article straight into the system section, because the article came out of your own Postgres and nobody ever thought of it as input. Covers auditing every slot a template fills by walking back to whoever could write those bytes, why sensitivity and trust are separate axes, how retrieval bait gets a poisoned document selected, and giving untrusted values a type so the renderer refuses a bare string.

  4. 4

    Prompt injection

    A hardened renderer guarantees that the message keeps the shape the application built, and a pentester extracts the escalation matrix anyway with one ordinary sentence submitted through the support form. Covers what the prepared-statement analogy gives you and what it withholds, sorting a finding list by whether the submitted characters changed the message's shape or only its meaning, why replayed assistant turns are structural after all, and why an in-prompt instruction that takes extraction from 74 of 100 variants to 31 is a rate rather than a control.

  5. 5

    Containing a persuaded model

    Once persuasion is a standing property rather than a bug awaiting a patch, the exposure is set by three things you fully control: what the prompt holds for an attacker to extract, what the response is checked against on the way out, and what the model is allowed to cause. Covers moving thresholds behind tool calls and sensitive facts out of the context entirely, a canary and deterministic identifier checks with the streaming problem they create, session-derived tool arguments, and a fortnight sequenced by what each day changes about the finding.

  6. 6

    Redacted prompt logs and provenance

    A log holding the request id, template name, model id and response text cannot reproduce a prompt-rendering failure, because the failure lived in the substitutions and the log recorded only the name of the thing doing the substituting. Logging the whole rendered prompt instead creates a replica of your customer data with none of its controls and a longer retention. Covers emitting a redacted skeleton and a provenance list at render time — source record id, field name, class, length and a hash rather than the value — and the retention arithmetic that makes it signable.

  7. 7

    Template revisions and run manifests

    An eval scoring 82.4% on Tuesday and 74.0% on Thursday, with a template edit, a reindex and a model-alias change all landing on Wednesday, cannot be attributed by commit hygiene, because git bisect searches commits and three of the six inputs a run consumes are not in the repository. Covers content-addressed template revisions that no human can forget to bump, a run manifest that records every input including the model version the provider actually served, and the three places this versioning is overhead you should not pay.

External resources