Cross-Site Scripting (XSS)
Cross-Site Scripting (XSS) occurs when attacker-controlled content is interpreted as executable JavaScript in another user's browser context. Frontend engineers primarily prevent XSS by treating untrusted data as data, using safe DOM APIs, contextual output encoding, and appropriate browser security controls.
XSS is a client-side injection vulnerability. An attacker gets malicious input into a page or application, and the browser interprets that input as code rather than ordinary data. Depending on the application, the injected script may run with the privileges of the vulnerable site's origin.
A common frontend cause is inserting untrusted strings into HTML with APIs such as `innerHTML`. Safer alternatives include `textContent` for plain text and DOM construction APIs when markup is genuinely required. Frameworks can provide automatic escaping, but explicitly opting into raw HTML rendering can reintroduce risk.
XSS is commonly divided into stored, reflected, and DOM-based forms. Stored XSS persists attacker-controlled content on a server or other shared data store before it reaches victims. Reflected XSS is returned as part of an immediate response to attacker-supplied input. DOM-based XSS arises when client-side code takes attacker-controlled data and passes it into a dangerous browser sink.
The correct defense depends on the context in which data is inserted. HTML text, HTML attributes, URLs, CSS, and JavaScript have different parsing rules, so a generic escaping function is not safe for every context. Content Security Policy (CSP) can provide an additional layer of defense, but it should complement—not replace—safe coding practices.
For a frontend engineer, the practical rule is to keep untrusted values out of executable contexts. Prefer APIs that create text or structured DOM nodes, validate URLs before assigning them to sensitive properties, avoid evaluating strings as code, and understand which framework features bypass normal escaping.
Key points
- Treat all externally controlled data as untrusted until its safety is established for its specific context.
- Prefer `textContent` and structured DOM APIs over `innerHTML` for untrusted content.
- Know the difference between stored, reflected, and DOM-based XSS.
- Context-specific output encoding is required because browsers parse HTML, attributes, URLs, CSS, and JavaScript differently.
- CSP is a defense-in-depth control, not a substitute for eliminating unsafe injection sinks.
Interview questions
What is XSS, and how would you prevent it in a frontend application?
XSS occurs when untrusted content is interpreted as executable code in a user's browser. I would avoid putting untrusted values into HTML or other executable contexts and would prefer APIs such as `textContent` for plain text. I would rely on a framework's default escaping rather than bypassing it unnecessarily. I would also use CSP and other browser security controls as defense in depth.
What is the difference between stored, reflected, and DOM-based XSS?
Stored XSS involves malicious content being persisted and later served to users. Reflected XSS occurs when attacker-controlled input is incorporated into an immediate response. DOM-based XSS occurs when browser-side code reads attacker-controlled data and sends it to a dangerous sink. The distinction describes where the malicious data travels and where the unsafe interpretation occurs.
Why is `textContent` safer than `innerHTML` for displaying user input?
`textContent` treats the assigned value as text, so HTML syntax inside the value is not parsed into elements. `innerHTML` parses its value as HTML, which means attacker-controlled markup can create executable behavior when the resulting document permits it. Therefore, `textContent` is preferable when the requirement is simply to display text. If real HTML is required, the content needs an appropriate sanitization strategy for that context.
Can a Content Security Policy completely prevent XSS?
CSP can significantly reduce the impact of many XSS attacks by restricting which scripts the browser is allowed to execute. However, it is not a replacement for preventing unsafe injection, and its effectiveness depends on the policy and application architecture. A weak policy can leave important execution paths available. The primary defense should still be safe handling of untrusted data, with CSP providing an additional browser-enforced boundary.
How do you reason about XSS when data is inserted into different contexts?
I first identify the parser and execution context receiving the data rather than assuming one escaping rule applies everywhere. HTML text, attribute values, URLs, CSS, and JavaScript have different syntax and security properties. I then choose a context-appropriate safe API or encoding strategy and eliminate unnecessary transitions into executable contexts. Finally, I verify the complete source-to-sink data flow and add browser controls such as CSP where appropriate.