Skip to content
Go Basics: Escape Untrusted Text in HTML Templates

Go Basics: Escape Untrusted Text in HTML Templates

October 31, 2026

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 1

Run 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.go

Observed output:

<p>&lt;script&gt;alert(&#34;x&#34;)&lt;/script&gt;</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_dir

If the directory is not empty, inspect its remaining contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on