Cross-site scripting is a strange kind of betrayal. Your site, the one your users trust, turns around and runs the attacker's code inside their browser. Not your server. Their browser. Their session. Their logged-in tab.
And the browser has no reason to be suspicious. The script arrived from your domain, wrapped in your padlock, under your name. So it runs with all the trust your users handed you.
That is XSS in one breath. Let me slow it down.
The concept: output you forgot to escape becomes code
A browser reads HTML and runs any script it finds inside it. That is its entire job.
XSS happens when attacker-controlled text lands in your page without being treated as plain text. You meant to display a comment, a username, a search term. But you dropped it straight into the HTML, so the browser cannot tell the difference between the words you wanted to show and a script someone slipped in. If the input contains markup, the browser runs it.
Input becomes output becomes code. Same shape as SQL injection, different stage. There the database gets tricked. Here it is the browser.
Stored versus reflected, quickly
Two flavors you should know.
Reflected XSS bounces off a single request. The malicious input rides in on a crafted link or form, the server echoes it straight back into the response, and it fires for whoever clicked. It is a trap you have to lure someone into.
Stored XSS is worse. The payload gets saved. A comment, a profile bio, a support ticket. It sits in your database and fires for every single person who loads that page. One poisoned comment, thousands of victims. No link required.
There is a third kind, DOM-based, where the whole thing plays out in client-side JavaScript that writes untrusted data into the page. Same root cause. Untrusted data, trusted context.
What an attacker actually does with it
This is where people stop taking it seriously, so let me be blunt.
Once attacker script runs in your user's browser, it can read whatever that browser can. It can lift session tokens and ride in as that user. It can quietly rewrite the page, drop a fake login form over the real one, and harvest whatever gets typed. It can deface the site. It can act on the user's behalf, click their buttons, change their email, all while looking exactly like them.
Session theft is the big one. If a script can read the session, it can become the user. Game over, no password required.
The fix: escape, frameworks, CSP
Good news. We know how to stop this.
- Escape output based on context. Text going into HTML gets HTML-encoded, so markup renders as harmless characters instead of running. This is the core defense.
- Lean on your framework. React, Vue, and Angular auto-escape by default. Render user text the normal way and they turn it into text, not code. Use that behavior on purpose.
- Set a Content Security Policy. CSP is your safety net, a header that tells the browser which scripts are allowed to run. Even if something slips through, a tight policy can stop it from executing.
- Set HttpOnly on session cookies so client-side script cannot read them. That alone takes the most common session-theft path off the table.
The footguns
Here is where teams quietly reintroduce it. Every framework has an "I know what I am doing" door. In React it is dangerouslySetInnerHTML. In plain JavaScript it is innerHTML. The name is a warning, not a suggestion.
The moment you push raw, unescaped user content through one of those, every auto-escape protection you had is gone. If you genuinely must render user-supplied HTML, sanitize it first with a trusted, maintained library. Never trust it raw.
Your framework escapes by default for a reason. The day you switch that off, you had better be certain the input is clean. Because the browser will run whatever you give it, and it will never ask whose side it is on.
I am Ksatria Bintang Samudra, an AI-native full-stack engineer with a penetration-testing background and a deep interest in digital forensics, open to remote work worldwide. More of what I build at ksatriabintangsamudra.com.

Top comments (0)