Security in depth · Unit 20 · Lesson 4 of 27
Cross-site scripting
Understand browser parsing contexts, safe rendering, and why one encoding rule cannot fit every destination.
Helpful before thisHTTP and proxiesUnderstanding client-side JavaScript
After this lesson you can
- explain how untrusted data can become browser instructions
- choose controls for text, attributes, URLs, and rich HTML
- describe impact without assuming cookie theft or a fixed severity
A display name should appear as a name. Trouble begins when an application lets that value become instructions for the browser. Cross-site scripting, or XSS, concerns script execution in the application’s browser context. Rendering unwanted markup alone is not enough to establish script execution.
Start with the intended use
For plain text, prefer a text-oriented API such as textContent or a framework’s normal text rendering. If the application intentionally accepts rich HTML, it needs a maintained sanitizer with an explicit policy. Encoding all markup would preserve text but remove the requested formatting.
The same value can pass through several parsers. A control must match the final destination, not merely the place where the user entered it.
EXPLORE THE CONCEPT
Choose a rendering policy
The same profile has three types of content. Compare the control each one needs.
Display name
Treat the name as plain text with a text API or normal framework rendering. A name does not need to be parsed as HTML.
Website link
Parse the URL, enforce permitted schemes and destinations, and handle its components and HTML attribute context correctly. URL encoding alone does not decide what is allowed.
Formatted biography
If rich HTML is required, use a maintained sanitizer with a defined policy. Avoid later transformations that reintroduce unsafe markup; plain-text fields do not need this parsing step.
A simplified learning model. It connects to no systems and uses no real data.
Compare the three profile fields. The intended use determines the rendering policy, and nested parsers may require more than one kind of handling.
Four destinations, different decisions
| Destination | Design decision |
|---|---|
| HTML text | Use text APIs or context-aware HTML encoding. |
| HTML attribute | Use a fixed, innocuous attribute name, quoted values, and appropriate encoding. |
| URL | Parse and validate permitted schemes and destinations, then encode individual components correctly. |
| Rich HTML | Sanitize with a maintained allow-list policy; avoid unsafe modification afterward. |
A URL encoded for transport can still express a disallowed scheme or destination. URL encoding does not replace that policy. A URL placed in an HTML attribute also needs the appropriate attribute handling.
JavaScript string escaping alone is insufficient when data is embedded in an HTML script element: the HTML parser processes that element too. Prefer separate data delivery and established framework serialization that handles the surrounding context. Do not place untrusted values in event handlers or other executable positions. OWASP’s prevention guidance explains these context distinctions.
Delivery and execution are different dimensions
Reflected values arrive with a request; stored values persist and appear later. DOM-based XSS describes a client-side data flow into an unsafe sink. These categories can overlap: a stored value can later reach a DOM sink. A reflected issue does not always require a clicked link.
Scenario: the club profile
A club displays a member’s name as text, validates their website against allowed URL schemes, and sanitizes a formatted biography. These are three rendering decisions, not one universal escape function.
If an unsafe path remains, impact depends on the affected origin, reachable data, user permissions, browser controls, and required interaction. HttpOnly prevents access to that cookie through script APIs; it does not prevent all authenticated actions. CSP adds a useful layer, but does not replace correct rendering. Stored XSS is not automatically more severe than reflected XSS.
A useful finding identifies the data source, final context, affected audience, missing control, and supported impact without collecting someone else’s session.
Review the final consumer, not just the first filter
The fictional club supplies three design records. P1 renders a display name with a text API. P2 accepts a biography with a restricted rich-text policy, sanitizes it, then passes it to an unreviewed formatting plug-in. P3 builds a website link after checking an approved scheme and handling the attribute context.
P1 matches its plain-text purpose. P3 has both a destination policy and rendering treatment; neither replaces the other. P2 remains incomplete: the plug-in may change the markup or how it is interpreted. The record says its behavior is unreviewed, not that it is confirmed to execute a script.
PredictThe sanitizer’s own tests pass. What evidence is still needed for P2?
Review the complete path through the formatting plug-in to the final browser use. Establish that allowed formatting remains allowed and disallowed content does not become active through later transformations. A test of the sanitizer alone does not cover an unexamined consumer.
Choose the smallest content contract that serves the feature. If the biography only needs emphasis and lists, define those supported features and use maintained tools to enforce that policy. Avoid treating rich content as trusted merely because it passed through storage or a signed API response.
Verification needs positive and negative expectations. A display name should preserve legitimate punctuation as visible text. Permitted biography formatting should work, and unsupported formatting should be removed or rejected according to the documented contract. Link validation should apply the chosen destination policy as well as preserve correct rendering.
Record component versions and the final use in the result. This makes the decision reviewable when a plug-in changes. Cookie settings and CSP can reduce some consequences, but neither supplies the missing evidence about P2’s complete rendering path.
Terms you met
Check yourself
No timer. No penalties. Read the explanation and try again whenever you like.
This lesson’s questions have changed. Your reading progress is saved; review the updated questions.
-
P2 sanitizes a biography, then sends it through an unreviewed formatting plug-in. What is the most defensible conclusion?
Show the answer
Correct answer: The final rendering path needs review; script execution is not established by this record. Identify the missing evidence without inventing an observed impact.
-
A display name must preserve punctuation as visible text. Which design best fits that requirement?
Show the answer
Correct answer: Use a text-oriented rendering operation for the name. The feature needs text, so preserving it as text avoids granting markup meaning.
-
A website link is correctly HTML-attribute encoded. What separate decision remains?
Show the answer
Correct answer: Whether the parsed URL's scheme and destination meet the link policy. Encoding preserves a context; it does not choose which destinations the feature permits.
-
Which verification result best supports the rich-biography contract?
Show the answer
Correct answer: Supported formatting works and unsupported content is handled as specified after every transformation. The result evaluates both useful behavior and the boundary at the actual final consumer.
-
The session cookie is HttpOnly and CSP is enabled. What do those facts establish about P2?
Show the answer
Correct answer: Additional browser controls exist; the rendering path still needs its own evidence. Their scope differs from preserving the biography's allowed content contract.
Try it
- WriteFor P1, P2, and P3, name the intended content type and the control responsible for it. Write an acceptance note for P2 with one supported formatting expectation, one unsupported-content expectation, and the component change that would require review again.