Skip to content

VNX-GO-020: Use of template.HTML with potential user input

Detects template.HTML, HTMLAttr, JS, CSS, URL and Srcset conversions applied to request data, markup built with Sprintf or concatenation, a function parameter, or a variable assigned from one of those. Constant markup, sanitised or escaped values and executed template output are not reported.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-GO-020
Severity Medium
SARIF level warning
Kind sast
Languages Go
CWE CWE-79
CAPEC CAPEC-63
ATT&CK T1059.007
CWSS vector CWSS:1.0/TI:M/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags xss template html
Source internal/sast/rules/vnx-go-020.rego

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’).

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 reports a trusted-content conversion (template.HTML, HTMLAttr, JS, JSStr, CSS, URL, Srcset) whose argument is request data, markup built from a variable, a function parameter, or a variable assigned from one of those. Constant markup and sanitised values are not reported.

// FLAGGED: a request value marked safe
bio := r.FormValue("bio")
data["Bio"] = template.HTML(bio)

// FLAGGED: markup formatted from the request, two lines away
notFound := fmt.Sprintf("<b><i>%s</i></b> was not found", term)
data["Message"] = template.HTML(notFound)

// FLAGGED: a helper that marks whatever its caller passes as safe
"raw": func(s string) template.HTML { return template.HTML(s) },

// NOT FLAGGED: developer-written markup, or sanitised user markup
data["Banner"] = template.HTML(`<span class="beta">Beta</span>`)
data["Body"] = template.HTML(policy.Sanitize(body))

Remediation

  1. 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 automatically
  2. Sanitize 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))
  3. 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>
  4. Consider the context: Remember that different contexts require different escaping (HTML vs JavaScript vs CSS vs URL). The html/template package handles HTML context automatically when you don’t use template.HTML.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-go-020
// nosec vnx-go-020

Or record an ignore rule: vulnetix ignore add --rule vnx-go-020 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-go-020.rego, cli/internal/sast/docs/vnx-go-020.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.