A comment should appear as the writer's text. Cross-site scripting, or XSS, occurs when untrusted content can execute code in a page's browser context. The important boundary is where the application turns data into something the browser interprets as instructions or markup.
The Document Object Model, or DOM, is the browser's object representation of a page. A sink is a place where code inserts a value into that representation. Assigning textContent places text into a node; assigning innerHTML asks the browser to parse HTML markup. The harmless bold and italic examples demonstrate that difference without an executable payload.
Before you begin
- Recommended first
- Notes: scope, command, result, time, HTTP methods, headers, status codes, cookies, How a web app is put together, Same-origin policy, JavaScript enough to follow a few lines in a page
- Estimated time
- 16 min reading, 12 min activity. Setup and optional practice may take longer.
- Where you work
- Use the fictional page source, requests and policy models printed here in any current desktop or mobile browser. No server, login, framed account page or socket connection is required.
- Without a lab
- Read each source fragment or model and predict its result in notes. Identify the boundary, the supporting fact and one limit, then compare with the activity rubric. This does not reproduce a browser exchange. Reading works without JavaScript. Checking requires JavaScript and a secure context; saving additionally requires usable browser storage.
After this lesson you can:
- Define the point where text becomes markup.
- Predict the literal and parsed displays for the tea string.
- Apply the same prediction to the activity string lane.
01Example
Worked string <b>tea</b>. The activity string is <i>lane</i>.
String: the characters <b>tea</b>
Predicted text assignment: those characters, including the brackets.
Predicted markup assignment: the word tea in bold, tags not shown.
The parsed display was not observed.02Where the string lands
The owned source gets two paragraphs by their id values, literal and parsed. It puts the same string into each through a different property. In <b>tea</b>, <b> opens a bold element and </b> closes it. textContent keeps those characters visible; innerHTML interprets them as tags. The <i>lane</i> activity uses italic tags instead.
Use textContent for untrusted text in this paragraph. If an application intentionally supports HTML, it needs suitable sanitization and controls for that context. Output encoding must fit the specific insertion context; HTML text escaping is not a universal answer for script or URL contexts. A Content Security Policy can reduce some consequences but does not replace safe handling at the sink.
Reflected XSS returns submitted input in the response to the same interaction. Stored XSS saves the input and later serves it to a reader. DOM-based XSS concerns a client-side script handling data unsafely; these descriptions can overlap. Sanitization removes or rewrites unsafe markup according to a policy when HTML is intentionally allowed. Content Security Policy is a browser-enforced set of restrictions on resources and code execution.
Show an untrusted note as characters, and see the two assignments in the owned page do different jobs.Model. A teaching model drawn by hand. It shows the idea, not real tool output.
Read the full explanation below.
| Field | Text assignment | Markup assignment |
|---|---|---|
| Literal input | <b>tea</b> | <b>tea</b> |
| Assignment context | Text | Markup |
| Predicted display | <b>tea</b>, including brackets | tea in bold; tags not shown |
| Observation | Not observed | Not observed |
The literal input remains the same across contexts.
The literal input remains the same across contexts.
Follow the explanation
- The literal input remains the same across contexts.
- Text preserves the brackets as characters; markup predicts formatting. No parsed display was observed.
Read every state and value
1. The literal input remains the same across contexts.
- Selected Comparison
- same
- Highlighted Field
- Literal input
2. Text preserves the brackets as characters; markup predicts formatting. No parsed display was observed.
- Selected Comparison
- context
- Highlighted Field
- Assignment context · Predicted display
03Predict the lane string
Read the evidence before comparing with the activity rubric. Do not open the file in a browser for this checkpoint.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Desk note</title>
</head>
<body>
<p id="literal"></p>
<p id="parsed"></p>
<script>
var note = "<b>tea</b>";
document.getElementById("literal").textContent = note;
document.getElementById("parsed").innerHTML = note;
</script>
</body>
</html>
Predicted display.
The string in the script is the ten characters < b > t e a < / b > with the letters of tea between the tags.
textContent assignment: the literal paragraph shows those characters, including the angle brackets.
innerHTML assignment: a browser that parses HTML would show the word tea in bold and would not show the tags.
That parsed display was not observed.
Activity string, different from the script: <i>lane</i>
Predicted literal display: the characters <i>lane</i>
Predicted parsed display: the word lane in italic.
- Read the activity string on the text card. Write the characters.
- Write the predicted literal display and the predicted parsed display.
- Check: the literal display is the characters <i>lane</i>. The parsed display would be the word lane in italic, and it was not observed. The tea string is the worked example.
- Stop. Do not paste a third-party script into the page.
04Optional challenge
From the two words reflected and stored, say which one fits a note that was saved and later shown, and which fits a note that comes back only in this response. The rubric is those two placements. About ten minutes.
05Fresh cases for the checks
Use these fictional cases for the checks below. They have different inputs from the worked examples. Read them here or make notes; no installation or external service is needed.
DOM case A: fresh harmless string modelOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.
String value: <b>cedar</b>
P: paragraph.textContent receives that string.
Q: paragraph.innerHTML receives that string.
Rule: textContent keeps characters as text; innerHTML parses HTML markup.
No browser rendering or script execution was observed.
DOM case B: independent caller textOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.
The product requirement is to display every caller character literally in a paragraph.
The handler currently assigns caller text to paragraph.innerHTML.
The application does not need user-authored HTML formatting.
06Explain your evidence
Optional written self-check: explain one decision using the supplied case. No tool run is required.
Check your explanation: predict both receiving properties on the same harmless string; locate the insertion boundary; choose the sink for literal text; distinguish predictions from observed rendering and avoid claiming arbitrary script execution.
07Common mistakes
If the lane check shows a bold tea, you used the worked string.
If you treat the italic prediction as observed, the card says no browser was opened.
If you rely on a policy header and still assign the string as markup, the insertion point is unchanged.
References
- MDN: Node.textContent
Defines the node text property and contrasts text insertion with HTML parsing.
- PortSwigger: Cross-site scripting
Explains this mechanism, its impact and defenses. Required reasoning uses the original evidence printed on this page.