flawopen.com/xss/Go
Go XSS: Use html/template and Sanitize Rich Text
text/template, HTML fmt.Fprintf, and template.HTML can bypass escaping. Use html/template and bluemonday (CWE-79).
Think of a form printer that changes its safety stencil for the slot it is filling. Go html/template checks whether a value lands in HTML text, an attribute, a URL, JavaScript, or CSS and escapes for that context; text/template just prints it. Marking input as template.HTML says it is already trusted and skips that protection. Keep HTML output in html/template, and sanitize rich text before treating it as trusted.
Core Concepts & Subsystem Terms
html/template vs text/template- The two packages share an API, so swapping the import compiles cleanly.
html/templateescapes each value for the context it lands in: HTML text, attribute, URL, JavaScript or CSS.text/templateescapes nothing, so{{.}}inserts raw markup. Usehtml/templatefor values rendered into an HTML response; usetext/templateonly when the destination is genuinely plain text. Usetext/templatefor non-HTML output such as configuration, plain-text email, or code generation. Do not treathtml/templateas a no-op for plain text: it may encode characters such as&. Choose the package based on the output format. template.HTML / JS / URL- Typed strings such as
template.HTML,template.JSandtemplate.URLtellhtml/templatethe value is already safe and must not be escaped. Converting user input to one of them, as intemplate.HTML(r.FormValue("bio")), disables the protection for that value. fmt.Fprintf to http.ResponseWriter- Writing HTML directly with
fmt.Fprintf,io.WriteStringorw.Writebypasses templates entirely.net/httpalso sniffs the Content-Type when none is set, so a response starting with markup is served astext/html. #ZgotmplZ- When
html/templatesees an unsafe value in a URL attribute, such asjavascript:alert(1)inhref, it replaces it with#ZgotmplZ. Seeing that string in output means the filter caught something; wrapping the value intemplate.URLto make it go away reopens the hole. bluemonday- An allowlist HTML sanitizer for Go.
bluemonday.UGCPolicy()keeps formatting, links and images but strips scripts, event handlers andjavascript:URLs. Sanitize first, then convert the result totemplate.HTML.
Framework mappings
- CWE reference for the weakness described in this guide: CWE-79
- OWASP Top 10:2025 — A05:2025 Injection
Step-by-Step Attack Flow
Input arrives in a request
A search link carries q, a greeting page reads name, and a profile form posts bio. A victim can be sent a crafted link, or a stored bio is shown to every visitor.
The value escapes escaping
text/template inserts it raw, fmt.Fprintf writes it straight into the body, or template.HTML marks it trusted. The benign test value <img src=x onerror=alert(1)> reaches the response unchanged.
The browser parses it as markup
The page contains a real <img> element. If the image resource fails to load, onerror runs alert(1).
The script runs as the site
Go handlers should keep untrusted values in html/template data positions, where contextual escaping applies. text/template, direct fmt.Fprintf HTML output, and template.HTML bypass that protection.
Source Code: Flaw vs. Secure Implementation
// handlers.go: each handler below lets request text reach the page as HTML
package web
import (
"fmt"
"html/template"
"net/http"
texttemplate "text/template"
)
// text/template has the same API but escapes nothing
var results = texttemplate.Must(texttemplate.New("r").Parse(`<h1>Results for {{.}}</h1>`))
func Search(w http.ResponseWriter, r *http.Request) {
results.Execute(w, r.URL.Query().Get("q"))
}
// Fprintf writes the name straight into the HTML body
func Greet(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<p>Hello, %s</p>", r.URL.Query().Get("name"))
}
// template.HTML tells html/template not to escape the bio
var profile = template.Must(template.New("p").Parse(`<div class="bio">{{.Bio}}</div>`))
func Profile(w http.ResponseWriter, r *http.Request) {
profile.Execute(w, struct{ Bio template.HTML }{template.HTML(r.FormValue("bio"))})
}
// handlers.go: html/template escapes by context; rich text is sanitized
package web
import (
"html/template"
"net/http"
"github.com/microcosm-cc/bluemonday"
)
// html/template escapes the value for the HTML text context
var results = template.Must(template.New("r").Parse(`<h1>Results for {{.}}</h1>`))
func Search(w http.ResponseWriter, r *http.Request) {
results.Execute(w, r.URL.Query().Get("q"))
}
// a template instead of Fprintf, with an explicit Content-Type
var greeting = template.Must(template.New("g").Parse(`<p>Hello, {{.}}</p>`))
func Greet(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
greeting.Execute(w, r.URL.Query().Get("name"))
}
// bluemonday strips scripts before the value is trusted
var profile = template.Must(template.New("p").Parse(`<div class="bio">{{.Bio}}</div>`))
var ugc = bluemonday.UGCPolicy()
func Profile(w http.ResponseWriter, r *http.Request) {
clean := ugc.Sanitize(r.FormValue("bio"))
profile.Execute(w, struct{ Bio template.HTML }{template.HTML(clean)})
}
Engineering & System Hardening Checklist
- ✓Fail CI when any package serving HTML imports
text/template, and enable gosec G203 to flag conversions totemplate.HTML,template.JSandtemplate.URL. - ✓Render every HTML response through
html/template; never write markup withfmt.Fprintf,io.WriteStringorw.Write. - ✓Pass user-supplied rich text through
bluemonday.UGCPolicy().Sanitize()(or a stricter custom policy) before converting it totemplate.HTML. - ✓Set
Content-Type: text/html; charset=utf-8explicitly andX-Content-Type-Options: nosniffon every response. - ✓Send a Content-Security-Policy with a nonce-based
script-srcand no'unsafe-inline', and set session cookies withHttpOnly,SecureandSameSite.
Sources
- CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
- Go — html/template package (contextual autoescaping)
- Go — html/template: Security model and typed strings
- bluemonday — HTML sanitizer for Go
- gosec — G203: use of unescaped data in HTML templates
- OWASP Cross Site Scripting Prevention Cheat Sheet
- Go standard library — text/template