Safeguard
Application Security•

How does server-side template injection apply to Go templates?

In Go, template injection comes from letting users write the template instead of fill it. Learn the text/template and html/template split and the fix.

Safeguard Security Team
4 min read

Server-side template injection (SSTI) happens when an application builds a template from user input and then runs it, so the user's text is treated as template code. In Go, the safe pattern is a fixed template written by developers, with user input passed only as data, and html/template for any HTML output.

What is the trust model in Go's template packages?

The Go standard library has two packages with the same interface: text/template and html/template. The documentation for html/template states its security model plainly: it assumes template authors are trusted and that the data passed to Execute is not.

That sentence is the whole story of SSTI in Go. Template text is code. If the author is you, fine. If the author is a visitor to your website, you have handed them a programming language running on your server.

The documentation also says html/template should be used instead of text/template whenever the output is HTML. It performs contextual autoescaping, so a value placed inside HTML body text, an attribute, a JavaScript string or a URL is encoded for that context.

Are cross-site scripting and template injection the same thing?

No, and mixing them up leads to the wrong fix.

  1. Cross-site scripting (XSS) is untrusted data ending up in a page as active content in the visitor's browser. html/template's autoescaping is built to stop this. Its docs show a script tag passed as data coming out as harmless escaped text.
  2. Server-side template injection is untrusted text becoming the template itself, executed on the server. Autoescaping of the data does not help here, because the problem is not the data. It is the template source.

Using html/template does not repair an SSTI bug where user text is parsed as a template. It only protects the output of templates you wrote.

What does the vulnerable shape look like?

A constructed example, described in words and not as exploit code. A customer email tool lets a marketing manager type a message with placeholders such as the customer's first name. The developer wants this to be flexible, so the handler takes the message string from the form and passes it to the template parser, then executes it with a customer record. Anyone with access to that form, or anyone who can influence the saved message, now decides what the template does. Which data and methods are reachable depends on what the developer passes into the template as its data value, and rich objects with methods make it worse.

The mistake is in the first step: calling a parse function on a string that came from outside.

What is the safer design?

Instead of letting users write templates, give them a closed set of placeholders and fill them yourself.

tpl := template.Must(template.New("mail").Parse(fixedMailTemplate))
data := struct{ FirstName, Message string }{customer.First, userMessage}
err := tpl.Execute(w, data)

Here the template text is a constant in your source, and the user's words arrive only as data fields. If a message needs placeholders, replace a short, fixed list of tokens with plain string substitution after validating the message, and do not hand the user a general template language.

Other practices that reduce the damage if a mistake slips in:

  • Pass simple structs with only the fields the template needs, not database models or objects with powerful methods.
  • Keep template files in the repository and load them at start-up, rather than reading them from user-writable locations or databases that users can edit.
  • Review any call to a template parse function that takes a variable, not a constant.

How to check or apply this

  1. Search your Go code for calls to Parse, ParseFiles and ParseGlob on both template packages.
  2. For each call, trace where the string or file comes from. If it can be influenced by a user, a tenant, or a stored setting editable in the UI, treat it as a finding.
  3. Confirm HTML output uses html/template and not text/template.
  4. Replace user-authored templates with fixed templates plus a short list of allowed placeholders.
  5. Narrow the data passed to Execute to plain structs holding only what the template needs.
  6. Add a test that submits template-like text as input and asserts that it appears literally in the output.

Quick check

Does html/template prevent server-side template injection?

No. It escapes data placed into templates you wrote. If untrusted text is parsed as a template, the template source itself is attacker-controlled.

When is text/template acceptable?

For non-HTML output such as plain-text email or config files, with a fixed template. The Go docs say to use html/template whenever the output is HTML.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.