How to Explain It in an Interview
Cross-site scripting, or XSS, happens when attacker-controlled data is treated as executable browser content instead of ordinary data. The attacker's code then runs in the vulnerable application's origin. That means it can interact with the page using the same browser origin as the legitimate application and may read JavaScript-accessible data or perform actions available to the current user.
1. Reflected XSS
Suppose the application receives a search request such as ?q=.... The attacker controls the q value. A trusted server reads that value and builds an HTML response containing it. If the server places the value into an HTML, attribute, script, URL, or another executable context without the correct context-specific encoding, the browser parser can interpret attacker-controlled data as markup or code.
The attacker-controlled input is the search query. The origin is the vulnerable application's origin. The victim is typically a user who opens an attacker-crafted URL, possibly while authenticated. The parsing sink is the location in the generated response where attacker-controlled data reaches an HTML or related browser parser. Protected assets include private page data, authenticated capabilities, and JavaScript-readable credentials or tokens. A session cookie marked HttpOnly cannot be directly read by injected JavaScript, but XSS can still make same-origin requests or perform actions using the victim's authenticated browser session.
Prevention must occur when the server generates the response. Treat the search query as data and apply contextual output encoding for the exact destination. For normal visible text, render it as text rather than executable HTML. Input validation can reject values that violate business rules, but filtering suspicious characters is not a complete XSS defense.
2. Stored XSS
Suppose an attacker submits a profile comment. The attacker controls the comment. The trusted server saves it in persistent storage. Later, another visitor opens the profile and the stored value is rendered. If that value reaches an executable HTML context or unsafe client-side sink without safe handling, attacker-controlled content can execute in the visitor's browser.
The attacker-controlled input is the saved comment. The origin is the vulnerable application's origin. The victim is any later visitor whose browser renders the unsafe value. The sink may be a server-generated HTML position interpreted by the browser parser or a later client-side DOM operation that turns the stored value into HTML. Protected assets include each visitor's JavaScript-accessible data and authenticated capabilities.
If profile comments should be plain text, render them as text using contextual encoding, textContent, safe DOM APIs, or framework escaping. If the application intentionally supports limited rich HTML, sanitize that HTML with a well-maintained allowlist-based sanitizer before it reaches an HTML-capable sink. Sanitization is appropriate when HTML is intentionally allowed; it is not a universal replacement for context-specific output encoding.
Stored XSS describes persistence, not necessarily the final execution mechanism. For example, the server could safely return a stored comment in JSON, but client JavaScript could later introduce XSS by assigning that comment to innerHTML.
3. DOM-based XSS
Suppose the page reads location.hash, which contains the URL fragment after #. The attacker controls the fragment. URL fragments are normally handled in the browser and are not included in the HTTP request sent to the server. Client JavaScript reads the fragment and passes it to an unsafe sink such as innerHTML. The browser then parses that string as HTML, which can create attacker-controlled executable content.
The attacker-controlled input is the URL fragment. The origin is the vulnerable page's origin. The victim is the user who opens the crafted URL. The source is the browser-side value read from location.hash. The sink is the client-side DOM or HTML-parsing API that interprets the value as markup. Protected assets include page data, JavaScript-readable tokens, and authenticated actions available from that origin.
Prevention must occur in the client-side data flow. If the fragment is supposed to be displayed as text, use textContent, createTextNode, safe DOM APIs, or framework rendering that escapes text by default. Do not pass untrusted strings to innerHTML, outerHTML, insertAdjacentHTML, or similar HTML-parsing sinks. If application functionality genuinely requires HTML, sanitize the content with an appropriate HTML sanitizer before using the required HTML sink.
Server response generation versus client-side DOM execution
The important distinction is where attacker-controlled data becomes executable browser content. With reflected XSS, the server commonly puts unsafe attacker-controlled data directly into the current response. With stored XSS, attacker-controlled data is first persisted and later becomes unsafe when a server or client rendering path interprets it as executable content. With DOM-based XSS, client JavaScript itself reads an untrusted source and sends it into an unsafe DOM sink, so the original server response can be completely safe.
This is why the best analysis follows data from source to sink. The storage location alone does not determine the final execution mechanism. A stored value can later cause DOM-based XSS if browser JavaScript handles it unsafely.
Primary defenses
For plain text, keep untrusted values as text. On the server, use output encoding appropriate to the exact context. In browser JavaScript, prefer textContent, createTextNode, safe attribute APIs, and framework rendering that escapes values by default.
Avoid APIs that interpret untrusted strings as HTML. innerHTML, outerHTML, insertAdjacentHTML, and similar sinks should not receive untrusted content. Framework escape-bypass features require the same caution because they deliberately disable normal escaping.
When the product intentionally supports user-authored HTML, use a well-maintained sanitizer with a strict allowlist of permitted elements, attributes, and URL forms. Do not try to create a complete XSS defense by manually removing suspicious strings such as <script> because dangerous browser parsing behavior is broader than one tag or pattern.
Defense in depth
Content Security Policy, or CSP, can reduce the impact of an XSS mistake by restricting which scripts may execute. A strong nonce- or hash-based CSP is valuable, but CSP does not replace safe source-to-sink handling.
Trusted Types can add another browser-side protection layer by restricting selected dangerous DOM sinks so that ordinary strings cannot be passed to them without going through approved policies. Trusted Types is defense in depth and still requires correct application logic and carefully designed policies.
Sensitive session cookies should normally use HttpOnly, Secure, and an appropriate SameSite setting. HttpOnly prevents injected JavaScript from directly reading the cookie value. It does not make XSS harmless because malicious same-origin code may still perform authenticated actions from the victim's page.
CSRF is a different security problem. CSRF tricks a browser into making an unwanted authenticated request, while XSS executes attacker-controlled content in the application's origin. XSS can frequently defeat normal CSRF assumptions because the injected script is already running within the trusted origin.
CORS and the same-origin policy are also not primary XSS defenses. The same-origin policy limits cross-origin access, but successful XSS runs with the vulnerable application's own origin privileges. CORS controls whether selected cross-origin responses can be read by requesting origins; it does not make unsafe same-origin rendering safe.
The frontend must never contain server secrets or long-lived privileged credentials. Browser storage such as localStorage is readable by JavaScript running in the same origin, so successful XSS can expose sensitive values stored there. Prefer designs that minimize sensitive credentials available to frontend JavaScript.
Third-party scripts and dependencies can also increase XSS and supply-chain risk because scripts running in the page can receive significant application privileges. Minimize unnecessary third-party code, review dependencies, keep them updated, and use controls such as CSP and Subresource Integrity where appropriate. These controls support the primary XSS defenses rather than replacing them.
Clickjacking is separate from XSS and is not central to these three attack paths. If relevant to the application's broader security design, prevent unwanted framing with CSP frame-ancestors or equivalent framing protections.
Safe failure and verification
If untrusted content cannot be safely rendered, fail closed by displaying it as inert text or rejecting the unsupported rich-content operation rather than falling back to raw HTML. Security logging should record enough information to diagnose failed validation, sanitization, CSP violations, or unsafe rendering attempts, but it must not record session tokens, secrets, or unnecessary personal data.
Verify each control with automated and manual tests. Use harmless test strings containing HTML-significant characters and markup-like content, then confirm that they appear as inert data instead of being interpreted as executable content. Test the reflected search-query path, the stored-comment path for later visitors, and the client-side URL-fragment path separately. Review server templates for correct context-specific encoding, search frontend code for unsafe DOM sinks, test sanitizer configurations when HTML is intentionally allowed, and monitor CSP or Trusted Types reports during development and controlled rollout.