For a template-injection alert, ask whether untrusted text becomes template source or only a value passed to a trusted template. A database field containing braces or template-looking characters is not enough. Locate the call that compiles or loads the template, trace who can change that source, and identify what data and functions the renderer exposes. If a user can influence text before it is compiled as a template, the claim merits independent review. If the source is fixed and the user text is passed only as a variable, the claimed server-side template injection may be unsupported—though browser output encoding still needs its own review.
MITRE's CWE-1336 defines the problem around externally influenced input interpreted as template expressions or directives. Jinja's API documentation makes the boundary visible: get_template loads a named template through a loader, while from_string compiles the supplied string as template source. The dangerous property is not the spelling of either method by itself; it is who controls the string or selected template in the actual caller.
Follow an invented notification preview
Illustrative service and code. No template expression was executed as an attack. Cedar Reports lets a team member preview an email notification. A scanner flags the preview handler:
def preview_notification(env, draft):
source = "<h1>Update</h1><p>" + draft.message + "</p>"
return env.from_string(source).render(account_name=draft.account_name)
The code intends draft.message to be paragraph text, but places it inside the string passed to from_string. Jinja treats that whole string as template source. A reviewer still needs to establish whether an ordinary team member can edit draft.message, whether this handler is the deployed preview path, and what variables or callables the environment makes available. Those facts determine whether this is a supported template-injection path and what the effect could be. The scanner's method name plus a line citation is a lead, not proof of the actor and consequence.
Now compare a design that keeps template source application-owned:
TEMPLATES = {
"status": "status-email.html",
"billing": "billing-email.html",
}
def preview_notification(env, template_key, draft):
filename = TEMPLATES.get(template_key)
if filename is None:
raise ValueError("unsupported template")
template = env.get_template(filename)
return template.render(
message=draft.message,
account_name=draft.account_name,
)
This example assumes the named files and every loader search directory are controlled by the application, and that the renderer has an appropriate HTML autoescaping configuration. A fixed filename is not a fixed template if a tenant-writable directory can shadow that name. Those assumptions must be checked, not inferred from the example. Jinja's API guide recommends explicitly configuring autoescape for HTML templates and explains that it is not enabled by default in every setup. Passing message as a variable avoids compiling its content as Jinja source; autoescaping addresses a different boundary when that message appears in HTML.
Suppose the template contains a “safe” filter around message or renders it into a JavaScript block. The server-side template-injection claim may still be false, while an XSS claim could remain. Jinja's template documentation explains how safe marking interacts with escaping, and OWASP's XSS guidance requires controls for the actual browser output context. Do not merge the two verdicts into one generic “template security” label.
When the product really does support template authors
Moving a message into a variable solves Cedar's ordinary-note requirement. It does not solve a different product requirement: an administrator may deliberately author a billing template containing fields and conditional sections. Here compilation is intended. The question becomes what authority that small language receives.
Consider two proposed render contexts:
A: {account: live_account_object, database: connection, helpers: all_helpers}
B: {account_name: "Cedar", invoice_number: "I-17", total_label: "$42.00"}
These are illustrative inputs, not executable sandbox configurations. Context A exposes objects whose methods and data access need extensive scrutiny. Context B is a small projection prepared under the application's authorization rules. A template author who may view I-17 should not gain an object that can fetch every account's invoices simply because rendering occurs in a worker.
A concrete design decision is to permit approved billing-template authors to use a maintained, restricted environment with a documented set of fields, filters and syntax. Supply only the approved projection; bound CPU, memory and output; handle compilation/render errors without publishing a partial message. Jinja's sandbox guidance makes clear that the sandbox alone does not provide these resource or data boundaries. This article does not supply a production-ready sandbox policy.
Follow preview approval into asynchronous delivery
Cedar's first repair makes preview use the fixed file. The sending worker still uses:
load draft → concatenate draft.message into template source → compile → send
The preview result therefore cannot close the delivery finding. The source packet now contains two conclusions: preview repaired, asynchronous message path still compiles ordinary user text. Route names and shared database storage do not make the renderers equivalent.
For deliberate authoring, record the immutable template version approved for delivery. Suppose an author previews T-8/v3, then edits the draft to v4 after approval. A job storing only template_id=T-8 may render the current draft rather than the approved source. The narrow remedy is to bind the job to the approved version and its relevant environment/policy version; if that version is revoked, the worker denies or obtains a fresh approval according to the product's policy. An approval badge without version binding is incomplete evidence.
The filled outcome could be:
| Path | Source author and render input | Decision |
|---|---|---|
| Ordinary note preview | Fixed file; message is a value | Template-source claim rejected after checking loader and caller |
| Ordinary note email | Message concatenated before compilation | Source-supported issue remains in worker |
| Authorized template authoring | Approved version and minimal data projection | Intentional evaluation; sandbox, authority and resource review required |
| Browser display of rendered output | HTML from either route | Separate output-context review; autoescape is not permission enforcement |
These are synthetic dispositions, not performed checks. The distinction helps the team preserve useful customization without granting every message writer template-author authority. It also shows exactly why fixing one compile call is insufficient when the same feature has two rendering environments.
Validate the repair and preserve the distinction
For Cedar's illustrative preview, move draft.message into a variable of an application-owned template and configure HTML autoescape for the template type. Review the email and preview routes at a new pinned revision; they might use different renderers. In an authorized test environment, a harmless synthetic value containing template delimiters can help confirm that the value remains literal data, while normal preview content still appears correctly. That functional check does not prove the entire environment safe, and this article does not claim it was performed.
If product requirements truly allow users to author templates, the design needs a separate permission and sandbox review, explicit limits, and a clear account of which data the author can access. Do not “fix” an ordinary message field by declaring every author trusted. If a scanner alert disappears after the change but a second route still compiles the field, the issue is not closed. If the source or deployment route is unavailable, mark the result inconclusive and name the missing artifact.
Trace the loader before calling a filename trusted
Cedar's fixed template map is useful only if the loader resolves those names to application-owned source. Inspect the configured search locations and their precedence in the actual environment. A map containing status-email.html establishes the selected name; it does not establish who can replace the file behind that name. The earlier shadowing warning becomes a concrete artifact request: loader configuration, template storage ownership, and the deployed version of the selected file.
The same question applies when templates live in a database or object store. Identify who can write a template, which account owns it, and which version a job retrieves. A database row is not inherently trusted template source because it is no longer in the HTTP request. Its author and the permissions around later edits determine the boundary.
For the ordinary-message feature, the requirement is simple: the message author may supply a value but may not alter the template program. For deliberate template authoring, the requirement is different and deserves a separate permission. Keeping those two operations distinct helps prevent a convenient preview endpoint from becoming an undocumented general-purpose rendering service.
Define what an author is allowed to learn
An approved billing-template author may need invoice number, amount, and account display name. They may not need a live account object with methods that traverse other records. The minimal projection in context B makes that decision reviewable. List the fields deliberately exposed and the source authorization used to obtain them before rendering begins.
Then inspect helpers and filters with the same rule. A harmless-looking formatting helper could obtain additional data or accept a resource identifier. The review should establish its actual behavior, not infer safety from its name. If a helper can read another object, it needs an appropriate authorization design; placing it in a renderer does not remove the application's account boundary.
This is why the rendering context belongs in the finding record. “User controls template source” identifies an important capability, but the effect depends on the language features, exposed objects, helper behavior, and process authority. State only the consequences the evidence supports. Do not automatically escalate every template-source issue to arbitrary command execution.
Review failures before a partial message is published
A template can fail during compilation, during value formatting, or after producing some output. Cedar should define whether any partial result may be previewed and ensure that a failed delivery does not send an unintended fragment to recipients. For a billing message, a clear failure state may be preferable to a partially rendered notice whose account or amount fields are missing.
The worker should preserve the approved template version and input projection in its nonsecret job metadata. If it retries, it should not silently select a newer draft with different source. A deliberate decision to retry using a corrected version should be recorded as such, with the current authoring and delivery policy applied.
For ordinary notes, an error handler should not fall back from a fixed template to compiling the original message string. That would restore the unsafe boundary only on malformed or unusual input, exactly where a happy-path preview would not reveal it. Inspect those branches at the same revision as the main repair.
Keep the final conclusion divided by interpreter
The repaired source may establish that ordinary message text is never compiled as Jinja source on the reviewed preview and worker paths. The HTML result still needs the right browser or email rendering policy. Intentional authored templates still need their own authority and resource limits. A single green “template safe” label would hide those different conclusions.
Give the developer a small acceptance packet: ordinary text remains a value, invalid template keys do not select arbitrary source, delivery uses the approved version, and errors do not invoke the old compilation helper. Qualified application validation can check those expectations later; this source-only walkthrough has not executed them.
The next action is to annotate the exact compilation call with “source author” and “rendered variable” for the next template alert. For the broader injection path model see reviewing input to sensitive operation. If the template is safe to compile but its HTML output is questionable, continue with modern frontend XSS review.