flawopen.com/Cross-Site Scripting/Go
Imagine a guestbook where visitors write a public note. Stored XSS is like someone writing a note that isn't just text — it's a hidden trick that makes the guestbook page itself start doing things, like stealing the next visitor's login session, the moment anyone opens the page to read it.
Cross-site scripting happens when untrusted input reaches the rendered page without proper output encoding. Go's standard library has a genuinely distinctive answer to this: two packages with an almost identical API — text/template and html/template — where only one of them actually escapes for HTML context.
In 2005, an 19-year-old user exploited a stored XSS flaw on MySpace to create the "Samy" worm — a script that added itself to every profile that viewed it, infecting over a million profiles within about 20 hours and forcing MySpace offline to contain it. It remains one of the most-cited demonstrations of how quickly stored XSS can self-propagate.
Source: widely documented in security industry retrospectives — see References below.// text/template does no HTML escaping
import "text/template"
t := template.Must(
template.New("w").Parse(
"<div>Welcome, {{.}}</div>"))
t.Execute(w, name)
// html/template escapes by context
import "html/template"
t := template.Must(
template.New("w").Parse(
"<div>Welcome, {{.}}</div>"))
t.Execute(w, name)
html/template parses the template and tracks where each {{.}} lands — inside an HTML body, an attribute, a <script> block, or a URL — and applies the correct escaping for that specific context automatically. text/template has the identical API but no awareness of HTML at all; it substitutes the value as plain text, verbatim.
Swapping "text/template" for "html/template" in an import line is the entire difference — code that compiles and runs identically either way, which means an accidental wrong import (e.g., copy-pasted from a non-HTML code generation use case) produces no compiler error, just a silent vulnerability.
It doesn't just HTML-encode everywhere — it detects whether a value lands in a URL, JS string, or CSS context within the template and escapes accordingly. This is a real Go-specific strength worth knowing, not just a gotcha to avoid.
Casting a string to template.HTML tells html/template "trust this, don't escape it" — the same escape-hatch pattern as every other language's raw-output mechanism, and just as dangerous when applied to untrusted input.
Only if it's specifically html/template — text/template is a real, commonly imported package with zero HTML awareness.
Any reflected value counts — a search query echoed on a results page, a URL parameter shown in an error message, a filename displayed after upload.
grep -rn '"text/template"' --include="*.go" .
Same underlying shape — untrusted data mixed into a command's structure without encoding — but the target and damage differ: SQL injection targets the database, XSS targets other users' browsers.
It covers the contexts it can detect from the template's static structure. Constructing a URL or JS context dynamically in a way the parser can't analyze can still bypass its protection — keep dynamic context construction to a minimum.