flawopen.com/Cross-Site Scripting/Go

Cross-Site Scripting in Go

High CWE-79 Draft — pending review
ELI5

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.

Key terms on this page
output encoding
Converting characters that have special meaning in HTML (like < and >) into harmless equivalents before inserting untrusted text into a page, so the browser displays it as text instead of running it as markup or script.
DOM
The in-memory tree structure a browser builds from a page's HTML — where an element's innerHTML is set determines whether inserted content is rendered as inert text or executable markup.

What's happening

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.

Real-world impact

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.

Vulnerable vs. fixed

VULNERABLE
// text/template does no HTML escaping
import "text/template"

t := template.Must(
  template.New("w").Parse(
    "<div>Welcome, {{.}}</div>"))
t.Execute(w, name)
FIXED
// html/template escapes by context
import "html/template"

t := template.Must(
  template.New("w").Parse(
    "<div>Welcome, {{.}}</div>"))
t.Execute(w, name)

Why the fix works

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.

Go-specific gotchas

The two packages are drop-in API-compatible — that's exactly the trap

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.

html/template's context-awareness is genuinely stronger than most languages' default escaping

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.

template.HTML and related types are explicit opt-outs

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.

Common misconceptions

"I'm using the template package, so I'm safe"

Only if it's specifically html/templatetext/template is a real, commonly imported package with zero HTML awareness.

"This only matters for user comments"

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.

How to check if you're affected

grep -rn '"text/template"' --include="*.go" .
Every hit is worth checking manually — text/template is legitimate for generating non-HTML output (config files, code generation); the risk is only when its output is ever written to an HTTP response.

Prevention checklist

FAQ

Is this the same bug as SQL injection?

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.

Does html/template protect against every XSS variant?

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.

References

View in: Python JavaScript Go Java PHP C# Ruby C/C++ Rust Kotlin Swift Solidity (N/A)