VNX-GO-020 – Use of template.HTML with potential user input
Overview
This rule flags instances where template.HTML is used to mark a string as safe HTML without proper validation or sanitization. When user-controlled data is passed to template.HTML, it can lead to Cross-Site Scripting (XSS) vulnerabilities because the HTML template engine will treat the input as safe and render it directly without escaping.
This maps to CWE-79: Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’).
Severity: Medium | CWE: CWE-79 – Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’) | OWASP ASVS: V1.2.1 – Output Encoding for HTTP Responses
Why This Matters
The template.HTML function in Go’s html/template package tells the template engine that the provided string is safe HTML and should not be escaped. When this function is used with user-controlled input (such as form data, URL parameters, or HTTP headers), an attacker can inject malicious JavaScript that will execute in victims’ browsers.
XSS vulnerabilities can lead to session hijacking, credential theft, defacement, or malware distribution. The vulnerability is particularly dangerous because it exploits the trust users have in a legitimate website.
What Gets Flagged
The rule flags any usage of template.HTML in Go source files, regardless of the input source. While some uses may be safe (e.g., hardcoded strings), the rule errs on the side of caution since determining whether input is truly trusted requires complex data flow analysis.
// FLAGGED: template.HTML used with request data
func handler(w http.ResponseWriter, r *http.Request) {
userInput := r.URL.Query().Get("comment")
tmpl, _ := template.New("page").Parse("<div>{{.}}</div>")
tmpl.Execute(w, template.HTML(userInput)) // User input marked as safe
}
// FLAGGED: template.HTML used with form data
func profileHandler(w http.ResponseWriter, r *http.Request) {
bio := r.FormValue("bio")
tmpl.Execute(w, template.HTML(bio))
}
Remediation
Avoid using template.HTML with untrusted input: Instead, let the template engine automatically escape your data by passing it directly:
// SAFE: Let template engine escape the input tmpl.Execute(w, userInput) // Will be HTML-escaped automaticallySanitize input before marking as safe: If you must allow certain HTML tags, use a proper HTML sanitization library:
import "github.com/microcosm-cc/bluemonday" // SAFE: Sanitize before marking as HTML sanitizer := bluemonday.UGCPolicy() safeHTML := template.HTML(sanitizer.Sanitize(userInput))Use template functions for specific needs: Rather than bypassing the safety mechanisms entirely, create custom template functions:
func safeLink(url string) template.URL { // Validate and sanitize URL here return template.URL(url) } // Then in template: <a href="{{.Link | safeLink}}">Click</a>Consider the context: Remember that different contexts require different escaping (HTML vs JavaScript vs CSS vs URL). The
html/templatepackage handles HTML context automatically when you don’t usetemplate.HTML.
References
- CWE-79: Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’)
- OWASP Application Security Verification Standard v4.0 – V1.2.1 Output Encoding for HTTP Responses
- OWASP Cross Site Scripting Prevention Cheat Sheet
- Go html/template package documentation
- Bluemonday HTML Sanitizer