XSS via io.WriteString to http.ResponseWriter

HIGH

User input flows into io.WriteString writing directly to ResponseWriter without HTML escaping — io.WriteString is a raw byte writer that performs no HTML neutralization.

Rule Information

Language
Go
Category
Security
Author
Shivasurya
Shivasurya
Last Updated
2026-04-13
Tags
gosecurityxssresponsewriterioreflected-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-003 --project .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
Cross-file analysis: 3 files

About This Rule

Understanding the vulnerability and how it is detected

`io.WriteString(w, s)` writes the string `s` directly to the writer `w` as-is, performing no transformation of any kind. When `w` is an `http.ResponseWriter` and `s` contains user-supplied data, every HTML character in that data reaches the browser unescaped, enabling Cross-Site Scripting attacks.

**io.WriteString vs other write methods**: `io.WriteString` is a convenience function that writes a string to an `io.Writer` — it is functionally equivalent to `w.Write([]byte(s))`. Neither applies any escaping. This distinguishes it sharply from `html/template`, which applies context-aware escaping based on where a value is being inserted (HTML body, attribute, URL, JS context).

Common patterns that introduce this vulnerability:

1. **Direct user input write**: ```go io.WriteString(w, r.FormValue("name")) ```

2. **String concatenation before write**: ```go io.WriteString(w, "<b>Hello " + r.URL.Query().Get("user") + "</b>") ``` The concatenation does not escape the user value; `io.WriteString` writes the result raw.

3. **Indirect write via helper function**: ```go func respond(w io.Writer, msg string) { io.WriteString(w, msg) } // Called as: respond(w, r.FormValue("q")) ``` The taint flows through the intermediate function. This is why inter-procedural analysis is needed to catch this pattern.

4. **Writer passed through middleware**: A `ResponseWriter` wrapped by middleware still implements `io.Writer`. If tainted data is written to a buffered wrapper and the buffer is later flushed to the original ResponseWriter, the XSS payload reaches the client.

**Why io.WriteString appears in Go web handlers**: Go's `net/http` handler signature (`func(w http.ResponseWriter, r *http.Request)`) naturally leads developers to write responses using low-level I/O primitives. The `http.ResponseWriter` interface embeds `io.Writer`, making `io.WriteString`, `fmt.Fprintf`, and `w.Write()` all interchangeable from a type perspective. Developers writing simple handlers often reach for these rather than `html/template`, especially for small responses like error messages, where they underestimate the risk.

**Error handler XSS — a common false sense of security**: Error responses are a frequent location of XSS via `io.WriteString`: ```go http.Error(w, "Not found: "+r.URL.Path, 404) ``` Even `http.Error` ultimately writes the message to the ResponseWriter. If `r.URL.Path` contains HTML characters, they are written to the 404 page. Real-world scanners have found this pattern in production Go services where developers assumed "it's just an error page" absolved them of escaping requirements.

**Relationship between this rule and GO-XSS-002**: GO-XSS-002 covers `fmt.Fprintf/Fprintln/Fprint`; this rule covers `io.WriteString`. Both are raw writers with no HTML awareness. The remediation is identical: replace with `html/template` or pre-escape with `html.EscapeString`.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Reflected XSS via URL Parameters

The most common io.WriteString XSS pattern: an HTTP handler reads a URL query parameter or form field and writes it back into the response (e.g., a "search results" page that shows what the user searched for). Attackers craft URLs with XSS payloads and distribute them via phishing emails, social media, or QR codes. Any victim who clicks the link executes the attacker's JavaScript in their browser.

2

Cookie and Session Token Theft

Injected JavaScript reads `document.cookie` and exfiltrates all cookies for the domain to an attacker-controlled endpoint via `new Image().src` or `fetch()`. If session cookies lack the `HttpOnly` flag, the attacker immediately hijacks the session without needing the user's password.

3

XSS in Error Pages

Error handlers that reflect the requested path or query string into 4xx/5xx responses are a common source of "low-severity" XSS that is exploitable in practice. A 404 page that says "The path /you-searched-for-X was not found" and reflects X without escaping is fully exploitable even if developers dismiss it as "just an error page."

4

Account Takeover via Admin Panel XSS

If io.WriteString XSS in an admin panel can be triggered by a regular user (e.g., by setting their username to a malicious payload stored in the database), visiting the admin panel with the user listed will execute the payload in the administrator's browser session, enabling privilege escalation.

How to Fix

Recommended remediation steps

  • 1Replace io.WriteString(w, userInput) with html/template rendering for all HTML responses.
  • 2If io.WriteString must be used, pre-escape all user values with html.EscapeString().
  • 3Set Content-Type: text/html; charset=utf-8 explicitly — never rely on auto-detection.
  • 4Add X-Content-Type-Options: nosniff to all responses to disable MIME sniffing.
  • 5Apply Content-Security-Policy header as a defense-in-depth layer.
  • 6Escape values in error pages and 4xx/5xx responses — they are equally exploitable.
  • 7For JSON API handlers, use encoding/json rather than string formatting into io.WriteString.
  • 8Pre-compile html/template at startup and reuse across requests for performance.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

Tracks taint from HTTP request sources (net/http.Request.FormValue, URL.Query, gin.Context, echo.Context, fiber.Ctx) to io.WriteString calls where the first argument is an http.ResponseWriter. Global inter-procedural scope tracks taint through helper functions that accept io.Writer parameters.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A03:2021 — Injection (XSS reclassified as injection subtype in 2021)
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. XSS is a required test category under penetration testing for in-scope applications.
NIST SP 800-53 Rev 5
SI-10 — Information Input Validation; SI-15 — Information Output Filtering
HIPAA Security Rule
§164.312(a)(1) — XSS enabling unauthorized access to ePHI via session hijacking

References

External resources and documentation

Similar Rules

Explore related security rules for Go

Frequently Asked Questions

Common questions about XSS via io.WriteString to http.ResponseWriter

`io.WriteString(w, s)` writes the string `s` directly to the writer `w` as-is, performing no transformation of any kind. When `w` is an `http.ResponseWriter` and `s` contains user-supplied data, every HTML character in that data reaches the browser unescaped, enabling Cross-Site Scripting attacks. **io.WriteString vs other write methods**: `io.WriteString` is a convenience function that writes a string to an `io.Writer` — it is functionally equivalent to `w.Write([]byte(s))`. Neither applies any escaping. This distinguishes it sharply from `html/template`, which applies context-aware escaping based on where a value is being inserted (HTML body, attribute, URL, JS context). Common patterns that introduce this vulnerability: 1. **Direct user input write**: ```go io.WriteString(w, r.FormValue("name")) ``` 2. **String concatenation before write**: ```go io.WriteString(w, "<b>Hello " + r.URL.Query().Get("user") + "</b>") ``` The concatenation does not escape the user value; `io.WriteString` writes the result raw. 3. **Indirect write via helper function**: ```go func respond(w io.Writer, msg string) { io.WriteString(w, msg) } // Called as: respond(w, r.FormValue("q")) ``` The taint flows through the intermediate function. This is why inter-procedural analysis is needed to catch this pattern. 4. **Writer passed through middleware**: A `ResponseWriter` wrapped by middleware still implements `io.Writer`. If tainted data is written to a buffered wrapper and the buffer is later flushed to the original ResponseWriter, the XSS payload reaches the client. **Why io.WriteString appears in Go web handlers**: Go's `net/http` handler signature (`func(w http.ResponseWriter, r *http.Request)`) naturally leads developers to write responses using low-level I/O primitives. The `http.ResponseWriter` interface embeds `io.Writer`, making `io.WriteString`, `fmt.Fprintf`, and `w.Write()` all interchangeable from a type perspective. Developers writing simple handlers often reach for these rather than `html/template`, especially for small responses like error messages, where they underestimate the risk. **Error handler XSS — a common false sense of security**: Error responses are a frequent location of XSS via `io.WriteString`: ```go http.Error(w, "Not found: "+r.URL.Path, 404) ``` Even `http.Error` ultimately writes the message to the ResponseWriter. If `r.URL.Path` contains HTML characters, they are written to the 404 page. Real-world scanners have found this pattern in production Go services where developers assumed "it's just an error page" absolved them of escaping requirements. **Relationship between this rule and GO-XSS-002**: GO-XSS-002 covers `fmt.Fprintf/Fprintln/Fprint`; this rule covers `io.WriteString`. Both are raw writers with no HTML awareness. The remediation is identical: replace with `html/template` or pre-escape with `html.EscapeString`.
Use Code Pathfinder to scan your codebase: pathfinder scan --ruleset golang/GO-XSS-003 --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 io.WriteString 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