Skip to Content
Course content

197: Auto-Escaping with html/template

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've spent any time building web apps, you know that trusting user input is a recipe for disaster. The classic nightmare is Cross-Site Scripting (XSS), where a user injects a <script> tag into a form, and suddenly every other user visiting that page is executing arbitrary JavaScript. You could write a bunch of regex to scrub the input, but that's a losing battle. In Go, we have a much more elegant solution: the html/template package.

Building a simple profile renderer

Let's imagine we're building a basic user profile page. We have a User struct, and we want to display their name and a short bio. I'll start by defining a simple data structure and a template string that looks like standard HTML.

package main

import (
	"os"
	"text/template" // I'm starting here on purpose...
)

type User struct {
	Name string
	Bio  string
}

func main() {
	user := User{
		Name: "Alice",
		Bio:  "I love Go! ",
	}

	tmplStr := `<div>
	<h1>User: {{.Name}}</h1>
	<p>Bio: {{.Bio}}</p>
	</div>`

	tmpl, _ := template.New("profile").Parse(tmplStr)
	tmpl.Execute(os.Stdout, user)
}

Wait, I used the wrong package

I just ran the code above and noticed something terrifying. The output was exactly what I wrote in the Bio: <p>Bio: I love Go! <script>alert('Hacked!');</script></p>. If this were rendered in a browser, that alert box would pop up immediately.

My mistake was using text/template. While text/template is great for generating configuration files or emails, it has zero awareness of HTML. It treats everything as a raw string and just shoves it into the output. When you're dealing with the browser, this is a huge security hole.

Switching to contextual auto-escaping

The fix is simple: replace text/template with html/template. These two packages have nearly identical APIs, but html/template is "context-aware." It looks at where the variable is being placed—whether it's inside an HTML tag, an attribute, or a URL—and applies the appropriate escaping for that specific context.

package main

import (
	"os"
	"html/template" // Switched this from text/template
)

type User struct {
	Name string
	Bio  string
}

func main() {
	user := User{
		Name: "Alice",
		Bio:  "I love Go! ",
	}

	tmplStr := `<div>
	<h1>User: {{.Name}}</h1>
	<p>Bio: {{.Bio}}</p>
	</div>`

	tmpl, _ := template.New("profile").Parse(tmplStr)
	tmpl.Execute(os.Stdout, user)
}

Now, when I run this, the output changes to:

<div>
	<h1>User: Alice</h1>
	<p>Bio: I love Go! <script>alert('Hacked!');</script></p>
	</div>

Notice how the < and > characters were converted to &lt; and &gt;. The browser will now display the script as literal text rather than executing it. This happens automatically. You don't have to call an EscapeHTML() function on every single variable; the package handles it based on the surrounding HTML.

Handling "Trusted" HTML

Every now and then, you'll actually want to render HTML—maybe you're using a Markdown parser that generates <b> or <i> tags. If you use html/template, those will be escaped too. To tell Go "I trust this specific string, don't escape it," you wrap the string in the template.HTML type.

If I change my user bio to template.HTML("I love Go!"), the package will see that type and leave the tags intact. Just be extremely careful with that; only use it on data you've already sanitized yourself.




📋 Practical Task

Exercise: Creating a Secure Comment Previewer

You are building a preview feature for a blog comment section. Users can enter a comment, and you want to show them a preview of how it will look on the page.

Create a program that does the following:

  1. Define a Comment struct with a Username and a Text field.
  2. Create a template that renders these into a <div class="comment">, with the username in a <strong> tag and the text in a <span>.
  3. Test your program with a "malicious" comment: <img src="x" onerror="alert('XSS')">.
  4. Ensure you are using the correct template package so that the output is safely escaped and would not execute the JavaScript in a browser.
  5. Print the final rendered HTML to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.