Cross-Site Scripting (XSS)
debt(d5/e3/b5/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan, semgrep, and psalm — all specialist static analysis tools. The code pattern (echo $_GET[ without htmlspecialchars) is identifiable by semgrep/psalm but won't be caught by a default linter or the compiler. Runtime manifestation can be silent until a user or tester triggers the payload, but the tooling available brings it to d5.
Closest to 'simple parameterised fix' (e3). The quick_fix is a single-call swap — wrap output in htmlspecialchars($val, ENT_QUOTES, 'UTF-8') and add a CSP header. However, common_mistakes reveal that multiple context-specific fixes are needed (HTML vs JS vs CSS contexts, ENT_QUOTES flag, Blade {!! !!} replacements), making it slightly more than a one-liner but still within one component at a time — landing at e3.
Closest to 'persistent productivity tax' (b5). XSS applies to all web contexts (applies_to: web) and affects every place user-supplied content is rendered. Every template, every output point must be audited. It doesn't reshape the architecture, but it is a persistent tax across many work streams — every feature that renders user data must encode correctly, slowing down ongoing development.
Closest to 'serious trap' (t7). The misconception field states explicitly: 'Stripping HTML tags prevents XSS.' This is a widely-held but wrong belief. Encoded payloads, SVG, CSS, and event attributes survive strip_tags. Additionally, common_mistakes show multiple non-obvious traps: encoding at input vs output, HTML encoding being wrong inside JS contexts, and htmlspecialchars() without ENT_QUOTES leaving single-quoted attributes vulnerable. These contradictions with developer intuition push this to t7.
Also Known As
TL;DR
Explanation
XSS lets attackers inject JavaScript into pages viewed by other users. Reflected XSS triggers via a crafted URL; stored XSS persists in the database and fires for every visitor. Consequences include session hijacking, credential theft, keylogging, and full account takeover. Prevention requires context-aware output encoding — in PHP, htmlspecialchars() with ENT_QUOTES for HTML contexts, and different escaping for JS, CSS, and URL contexts.
How It's Exploited
# Executes in victim's browser, steals session cookie
Diagram
flowchart TD
ATK[Attacker] -->|stores script| DB[(Database<br/>or URL param)]
VICTIM[Victim visits page] --> SERVER[Server renders<br/>unescaped output]
SERVER -->|script in HTML| BROWSER[Browser executes<br/>attacker script]
BROWSER -->|steal cookie| ATK
BROWSER -->|keylog form| ATK
subgraph Fix
OUT[Output] --> ESC[htmlspecialchars<br/>Content-Security-Policy]
ESC --> SAFE[Inert text<br/>not executed]
end
style LEAK fill:#f85149,color:#fff
style SAFE fill:#238636,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Encoding input on the way in rather than on output — context determines the correct encoding, not the storage layer.
- Forgetting that HTML encoding is wrong inside JavaScript contexts — you need JS encoding there.
- Trusting htmlspecialchars() without ENT_QUOTES — single quotes in attributes remain dangerous.
- Marking user content as safe in templating engines (e.g. {!! $var !!} in Blade) without explicit sanitisation.
Avoid When
- Do not rely on input sanitisation alone — stripping tags on input does not prevent XSS on output.
- Do not use htmlspecialchars() for JavaScript, URL, or CSS contexts — each context requires its own escaping function.
When To Use
- Understanding XSS is essential for any developer rendering user-supplied content in a browser.
- Apply output encoding whenever displaying data from any untrusted source — database, API, user input.
Code Examples
// Reflected XSS — user input echoed directly
echo '<p>Hello, ' . $_GET['name'] . '</p>';
// Always escape output for the correct context
echo '<p>Hello, ' . htmlspecialchars($_GET['name'] ?? '', ENT_QUOTES, 'UTF-8') . '</p>';
// For JS context, json_encode is the correct escaper:
$data = json_encode($userInput, JSON_HEX_TAG | JSON_HEX_AMP);