Go Basics: Escape Untrusted Text in HTML Templates
A display name that contains markup should appear as text in a page. Use html/template and pass ordinary strings so its contextual escaping remains active.
Prepare a scratch directory
Tested on Linux amd64 with Go 1.26.10. Install Go and make go available on PATH. You also need Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. The example uses only the standard library and needs no module or external dependency.
Keep the same terminal open. Stop if setup fails, before saving the source:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Run the example
Save this as html-template-escaping-demo.go:
package main
import (
"bytes"
"fmt"
"html/template"
)
type Profile struct {
Name string
Query string
}
func main() {
page, err := template.New("profile").Parse(`<p>{{.Name}}</p><a href="/search?q={{.Query}}">Search</a>`)
if err != nil {
panic(err)
}
var output bytes.Buffer
data := Profile{Name: `<script>alert("x")</script>`, Query: "tea & cake"}
if err := page.Execute(&output, data); err != nil {
panic(err)
}
fmt.Println(output.String())
}Run it:
go run html-template-escaping-demo.goObserved output:
<p><script>alert("x")</script></p><a href="/search?q=tea%20%26%20cake">Search</a>Read the result
The same template engine treats text content and a URL query as different contexts. The script-shaped display name becomes escaped text, while the query is encoded for its position inside the link. Buffering before writing a response also avoids sending a partial template result when execution fails.
Apply it carefully
Template authors must be trusted. Keep user data in values rather than inserting it into template source. Do not cast untrusted strings to template.HTML or other trusted-content types to bypass escaping. This example demonstrates two contexts; it does not replace URL destination validation or application authorization.
The official Go reference documents the behavior used here.
Clean up
Remove the saved source, return to the starting directory, and remove the empty practice directory. go run does not leave an executable here; Go manages its build cache separately.
rm -- html-template-escaping-demo.go
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirIf the directory is not empty, inspect its remaining contents before removing anything else.
Acknowledgements
This post was created with assistance from AI.