Cyber QA
Testing for XSS: A QA Guide (2026)
Learn testing for XSS with context-aware probes, Playwright automation, reflected, stored, and DOM coverage, sanitization checks, and interview answers.
27 min read | 3,806 words
TL;DR
Testing for XSS requires tracing each untrusted value to its browser rendering context. Prove safety with inert markers and controlled browser oracles, then verify the exact encoder, URL policy, or sanitizer used by every consumer view.
Key Takeaways
- Treat XSS as a source-to-sink browser context problem, not a single script-tag test.
- Start with inert markers, identify the parser context, then use harmless execution proof only in authorized environments.
- Test reflected, stored, DOM, rich-text, file, error, admin, email, PDF, and web-view renderers separately.
- Use Playwright to observe the live DOM, execution markers, dialogs, errors, CSP reports, and unexpected network requests.
- Apply context-aware output encoding, parsed URL policy, or maintained HTML sanitization at the final renderer.
- Keep CSP, Trusted Types, cookie flags, and isolated content origins as defense in depth.
Testing for XSS means proving that untrusted data never becomes executable browser code in any rendering context. Good QA coverage follows each input from source to sink, uses a harmless execution marker in an authorized environment, checks the browser rather than raw HTML alone, and verifies context-appropriate output handling across server pages, single-page apps, rich text, URLs, and stored content.
Cross-site scripting is a family of browser injection defects, not a single <script> payload. An application can escape HTML text correctly and still be vulnerable inside an attribute, URL, JavaScript string, CSS value, SVG, or unsafe DOM API. This guide provides a repeatable, evidence-driven strategy for reflected, stored, and DOM-based XSS.
TL;DR
| Question | QA answer |
|---|---|
| What proves XSS? | Controlled input executes in an unintended browser context, demonstrated with a harmless in-page marker. |
| Is seeing payload text in HTML enough? | No. Reflection identifies a path, but execution depends on parsing context and browser behavior. |
| What is the primary prevention? | Context-aware output encoding, safe DOM APIs, and maintained sanitization when HTML is intentionally allowed. |
| Does CSP fix XSS? | No. A strong Content Security Policy limits impact and provides telemetry, but the unsafe sink still needs repair. |
| How should stored XSS be tested? | Insert through one path, trigger every renderer and role, then clean up the record and verify no side effect occurred. |
| What should automation observe? | Marker execution, dialogs, page errors, console, network egress, DOM context, and safe rendering of the literal input. |
1. Build the Testing for XSS Context Model
XSS occurs when attacker-controlled data reaches a browser interpreter as executable content under a trusted origin. The important question is not whether the application accepts angle brackets. The question is which parser receives the data and whether the application applied the correct protection for that specific context.
Common contexts include:
- HTML text between elements.
- Quoted or unquoted HTML attributes.
- URL-valued attributes such as
hrefandsrc. - JavaScript strings, template literals, object literals, and inline handlers.
- CSS values and style attributes.
- DOM APIs such as
innerHTML,outerHTML,insertAdjacentHTML, anddocument.write. - SVG, MathML, XML, Markdown, rich-text, and template output.
Output encoding is context-specific. HTML entity encoding protects ordinary HTML text, but the same transformed value is not automatically safe in JavaScript or a URL. Some contexts are too dangerous for untrusted data and should be redesigned rather than escaped.
Keep XSS distinct from HTML injection. Untrusted markup can alter layout, create phishing UI, or trigger requests without executing JavaScript. That still may be a valid security defect, but report the observed capability precisely. Also distinguish a reflected string in source from browser execution.
CSRF and XSS can combine. Script running in the trusted origin can often make same-origin requests and read anti-CSRF tokens, which is why testing for CSRF remains a separate but connected discipline.
2. Inventory Sources, Transformations, Sinks, and Renderers
Map where untrusted data enters and every place it is later rendered. Sources include URL parameters, fragments, path segments, forms, JSON, GraphQL variables, headers, cookies, uploaded filenames, EXIF metadata, Markdown, imported files, WebSocket events, postMessage, browser storage, third-party APIs, database records, and support tooling.
For each source, trace transformations. Validation may occur at the API, storage service, Markdown processor, serializer, frontend state layer, component library, and DOM sink. Double decoding and inconsistent normalization are common. Record whether the value is decoded from percent encoding, HTML entities, Unicode escapes, Base64, JSON, or a template language before rendering.
Then inventory sinks and renderers:
- Server-rendered page, email preview, admin console, dashboard, and public profile.
- React, Vue, Angular, Svelte, or legacy DOM code.
- PDF, print view, mobile web view, browser extension, and embedded widget.
- Rich-text editor preview and published content.
- Search results, autocomplete, validation errors, logs, and support tickets.
- Attribute values, links, image sources, style fields, and inline data bootstrapping.
Stored data must be followed across roles. A standard user's display name may appear safely in their profile but unsafely in an administrator moderation queue. A filename may be encoded in the upload page but injected into a download error. Give each test value a unique marker so you can identify the source and clean it up.
The API security testing with OWASP guide helps organize input surfaces beyond visible forms. XSS often begins in an API even though execution happens later in a browser.
3. Compare Reflected, Stored, and DOM-Based XSS
The categories describe how data reaches the sink, which changes the test workflow and affected users.
| Type | Data path | Typical trigger | Main test challenge | Cleanup need |
|---|---|---|---|---|
| Reflected XSS | Request input appears in the immediate response | Crafted link, form, or error | Identify exact response context and browser parsing | Usually no persistent record |
| Stored XSS | Input is persisted and rendered later | Profile, comment, ticket, filename, import | Find every viewer, role, and alternate renderer | Mandatory record cleanup |
| DOM-based XSS | Client JavaScript moves source data to an unsafe sink | Fragment, message, storage, API response | Server response may look safe while runtime DOM is unsafe | Depends on source persistence |
| Client-side template injection | Input is evaluated by a client template expression | Legacy template or dynamic compilation | Framework and version behavior vary | Depends on storage |
| Mutation XSS | Sanitized markup mutates into executable DOM after parsing | Browser reparsing or DOM manipulation | Serialized output can differ from live DOM | Usually stored or rich-text data |
The same defect can span categories. A stored value delivered by an API may become DOM XSS when frontend code assigns it to innerHTML. Report the data path and unsafe sink rather than arguing over one label.
Reflected testing begins with a unique inert marker and response-context analysis. Stored testing adds producer-consumer and role matrices. DOM testing needs runtime instrumentation, breakpoints, or automated browser observation. Static response search alone misses client-side construction.
Prioritize renderers used by privileged people, such as administrators, support agents, finance reviewers, and content moderators. Stored execution in a back-office tool can cross a major privilege boundary even when the submitting user has little authority.
4. Start with Inert Markers and Context Discovery
Use an identifier such as XSSPROBE-7f31 before using executable markup. Submit it through one source and search the raw response, live DOM, attributes, scripts, browser storage, and later views. Record every transformation exactly. If the marker is absent, identify whether it was rejected, normalized, encoded, truncated, or stored for another renderer.
When reflected, inspect the immediate characters around it. Is it HTML text, an attribute, JSON inside a script element, a URL, CSS, or a JavaScript string? Browser developer tools can show the live DOM, but also inspect raw response because the browser may repair malformed markup. Do not paste arbitrary exploit strings into every field.
Move to a harmless context-specific execution marker only in an authorized test environment. A useful marker sets a unique property on window, such as window.__xssProbe = "profile-name". It demonstrates script execution without reading cookies, storage, page data, or sending network requests. Avoid alert() in automation because dialogs can block tests and disrupt shared users.
Examples for a dedicated lab may include an image error handler for HTML context or a controlled URL scheme for a link context. Use the minimum string that tests the parser. Never use cookie exfiltration, credential capture, persistence outside the test record, or self-propagating code.
If the value does not execute, do not conclude all contexts are safe. The payload may simply be wrong for that parser, blocked by CSP, sanitized, or placed in a non-executable location. Review the sink and protection. A CSP violation plus an unsafe innerHTML assignment is still a root-cause defect even if the policy stops your marker.
5. Automate Testing for XSS with Playwright
The following Playwright test is fully local. It starts a small server that safely HTML-encodes a reflected query value, opens it in Chromium, and proves a harmless image-handler marker remains text. Install with npm install -D @playwright/test, install Chromium with npx playwright install chromium, save as xss.spec.js, and run npx playwright test xss.spec.js.
import http from "node:http";
import { test, expect } from "@playwright/test";
function escapeHtml(value) {
return value
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
let server;
let baseUrl;
test.beforeAll(async () => {
server = http.createServer((request, response) => {
const url = new URL(request.url, "http://127.0.0.1");
const query = url.searchParams.get("q") ?? "";
const html = `<!doctype html>
<html><body>
<h1>Search</h1>
<p id="result">${escapeHtml(query)}</p>
</body></html>`;
response.writeHead(200, {
"content-type": "text/html; charset=utf-8",
"content-security-policy": "default-src 'self'; script-src 'self'"
});
response.end(html);
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const address = server.address();
baseUrl = `http://127.0.0.1:${address.port}`;
});
test.afterAll(async () => {
await new Promise((resolve, reject) => {
server.close((error) => error ? reject(error) : resolve());
});
});
test("renders an HTML-context probe as literal text", async ({ page }) => {
const probe = '<img src=x onerror="window.__xssProbe=\'reflected\'">';
const pageErrors = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto(`${baseUrl}/search?q=${encodeURIComponent(probe)}`);
await expect(page.locator("#result")).toHaveText(probe);
await expect(page.locator("#result img")).toHaveCount(0);
expect(await page.evaluate(() => window.__xssProbe)).toBeUndefined();
expect(pageErrors).toEqual([]);
});
The explicit encoder is appropriate only for HTML text content. Do not reuse it in JavaScript, CSS, or URL contexts. In a real application, prefer the framework's default text rendering rather than manually building HTML. Extend the test with page.on("dialog"), request observation to an organization-owned sink, and CSP violation reporting when those are part of the approved test plan.
For stored XSS, create the record through the normal API, open every consumer view in isolated contexts, assert the marker never executes, then delete the fixture in afterEach. Ensure cleanup still runs after a failure.
6. Test HTML, Attribute, URL, JavaScript, and CSS Contexts
HTML text is usually protected by HTML entity encoding or safe text APIs. Verify literal rendering of <, >, &, quotes, and valid Unicode. In DOM code, textContent is appropriate for text, while innerHTML invokes the HTML parser. Framework interpolation is generally safe only while developers avoid raw HTML features and unsafe escape hatches.
Attributes need quoting and context-aware encoding. Test values in title, alt, value, data attributes, and dynamically generated names. Event-handler attributes such as onclick are JavaScript contexts and should not receive untrusted data. Unquoted attributes should be removed from templates rather than defended with a partial character list.
URLs require scheme and destination validation in addition to HTML encoding. Test href, src, action, formaction, poster, SVG links, and redirects. Allowlist required schemes such as https: and resolve relative URLs using a real URL parser. Encoding a dangerous scheme does not make navigation safe.
JavaScript contexts are high risk. Avoid placing untrusted data in inline scripts. If initial page data is needed, use a safe JSON serialization pattern and a non-executable data container or external script that reads text. Test closing sequences, line terminators, and parser transitions appropriate to the template. Never rely on backslash escaping alone inside an HTML script element.
CSS contexts can influence resource loading and UI integrity. Restrict choices to explicit values or validated tokens. Do not place arbitrary user text in style blocks or attributes. Modern browsers have removed some historical CSS execution paths, but unsafe URLs, overlays, and data disclosure can still matter.
7. Validate Framework Rendering and Dangerous DOM Sinks
Modern frameworks escape text interpolation by default, but developers can opt out. Inventory React dangerouslySetInnerHTML, Vue v-html, Angular trust-bypass APIs, Svelte {@html}, direct DOM assignment, third-party widgets, and legacy template compilation. Each escape hatch needs a documented content source and sanitizer.
Search runtime code for innerHTML, outerHTML, insertAdjacentHTML, document.write, string arguments to timers, dynamic script creation, and URL assignments. A source can be safe in one release and become untrusted after a feature change. Track the data flow rather than approving a sink forever.
DOM-based tests should cover location.search, location.hash, document.referrer, window.name, postMessage, local and session storage, IndexedDB, service-worker messages, and API responses. For postMessage, verify exact allowed origins and message schemas before data reaches a sink. The receiver must not trust * or suffix string checks.
Client-side routing creates subtle decode layers. Test percent-encoded and double-encoded markers through actual navigation, refresh, back, and deep links. Confirm the router and component do not decode into markup after the server encoded the original value.
Trusted Types can restrict assignment to dangerous DOM sinks in supported browsers. If enabled, test enforcement mode, policy names, report collection, third-party compatibility, and that developers did not create a permissive default policy. Trusted Types is defense in depth and a migration aid, not permission to pass arbitrary strings to a policy.
Use browser breakpoints or instrumentation in a debug build to identify the exact sink. Defect reports should name the source, transformation chain, and sink, such as location.hash -> decodeURIComponent -> results.innerHTML.
8. Test Rich Text, Markdown, SVG, and File Content
Applications that intentionally allow HTML need sanitization rather than blanket encoding. Use a maintained sanitizer configured for the actual browser output. QA should verify allowed tags and attributes remain usable while dangerous elements, event handlers, URL schemes, namespace changes, and malformed markup are removed.
Build a policy-oriented corpus, not an endless exploit list. Include representative headings, links, lists, emphasis, images if allowed, tables, code, disallowed scripts, event attributes, dangerous URLs, nested malformed markup, SVG or MathML if relevant, and comments. Store sanitized output or sanitize consistently on every render according to the architecture. Test upgrades because parser and sanitizer behavior changes.
Markdown is not automatically safe. Many renderers permit raw HTML or produce links and images. Verify the configured raw-HTML mode, link scheme policy, generated heading IDs, footnotes, embedded media, syntax highlighting, and plugins. Test both preview and published views because they may use different pipelines.
SVG is active content in some embedding modes. Upload policy should distinguish image use, direct navigation, inline embedding, and download. A file safe as a forced attachment may be unsafe when served as image/svg+xml from the application origin. Test Content-Type, Content-Disposition, X-Content-Type-Options, storage origin, and preview transformation.
Filenames and metadata can reach HTML through upload lists, validation errors, image galleries, content disposition, and administrative scanners. Use a uniquely marked filename in a disposable upload and trace every view. Delete files and derivative thumbnails after testing.
9. Use CSP, Trusted Types, and Cookie Flags as Defense in Depth
A strong Content Security Policy can block many script execution paths and provide violation telemetry. Prefer nonce- or hash-based script authorization, avoid broad host wildcards, and eliminate unsafe-inline where possible. Test the delivered policy on every HTML response, including errors, login, legacy pages, and CDN variants.
QA should check both enforcement and functionality. Capture CSP violations during normal user journeys, then exercise approved XSS markers and verify blocked execution creates a useful report without secrets. Report-only mode measures impact but does not prevent execution. Multiple policy headers combine restrictively, while a misplaced meta policy has limitations.
Do not close an XSS defect only because CSP blocked the current payload. The unsafe sink may enable HTML injection, same-origin gadget use, navigation, or execution in a page with a weaker policy. Fix output handling and retain CSP as a second barrier.
HttpOnly cookies prevent ordinary JavaScript from reading the cookie value, but an executed script can still perform authenticated same-origin actions and read page data. Secure and SameSite flags address other threats. Test them, but do not describe them as XSS prevention.
Subresource Integrity can protect externally hosted static scripts from unexpected modification when exact files are pinned, but it does not sanitize application data. Isolation headers and a separate origin for user content can reduce impact. Test actual origin boundaries, redirects, CORS, and cookies around the content domain.
10. Cover APIs, GraphQL, Errors, Emails, PDFs, and Web Views
An API returning JSON does not execute XSS by itself, but its data may enter many renderers. Seed a harmless marker through REST or GraphQL, then inspect web, mobile web view, admin, email preview, PDF, export viewer, and third-party integration consumers. The safest encoding point is usually the final output context, not permanent HTML encoding in storage.
GraphQL error messages and extension fields can reflect variables or backend errors. Test how GraphQL explorers, developer consoles, and product UIs render them. A server can safely return JSON while a custom error panel inserts the message with innerHTML. Inspect data and errors paths separately.
Validation errors often echo rejected values. Cover server and client validation, toast notifications, banners, field help, logs visible in support consoles, and localization templates. An error path may use a different rendering library from the success page.
HTML email has its own client restrictions and sanitizer behavior. Treat it as a separate renderer. Test subject, display name, link text, and template variables without sending to real customers. PDFs and print engines can load remote resources, interpret HTML, or expose internal URLs. Coordinate with the owning team and keep network observation inside an authorized sink.
Embedded web views and desktop shells may expose privileged bridges beyond normal browser JavaScript. A marker that executes there can have greater impact. Test navigation allowlists, context isolation, message bridges, local file access, and update channels with specialized owners.
The GraphQL API testing guide covers response envelopes and resolver behavior that help trace untrusted values before browser rendering.
11. Build Reliable XSS Regression Coverage
Keep a small corpus organized by context and business feature. Every case needs a unique marker, expected rendering, execution oracle, allowed network policy, and cleanup action. Do not run hundreds of payloads blindly in every UI field. Map one or two meaningful probes to each actual sink.
Layer the tests:
- Unit tests for context-specific encoding, URL validation, and sanitizer policy.
- Component tests for framework interpolation and raw HTML boundaries.
- API tests for storage fidelity and safe error shapes.
- Playwright tests for real browser parsing, DOM sinks, CSP, and cross-role stored rendering.
- Targeted DAST in an isolated authorized environment.
Browser assertions should observe window markers, dialogs, page errors, console messages, CSP reports, unexpected elements, and requests to an organization-owned test origin. Block all other external requests in the test context. A literal text assertion alone is not enough when the same value appears elsewhere in the page.
For stored cases, cleanup must be robust. Create through an API, render through the UI, and delete through a trusted fixture even after failure. Never leave an executable marker in a shared admin queue. Use test tenants whose records cannot reach production notifications.
Record framework and sanitizer versions in the test evidence without hard-coding assumptions into prose. When dependencies update, rerun the policy corpus and inspect changes in allowed output.
12. Report and Retest XSS Findings Precisely
An actionable report identifies the source, storage step if any, transformations, output context, exact unsafe sink, affected viewer, harmless execution proof, and realistic impact. Include raw response and live DOM excerpts only around the synthetic marker. Redact session data and unrelated page content.
State what executed. Setting window.__xssProbe proves JavaScript execution in the origin. It does not automatically prove cookie theft when cookies are HttpOnly, or administrator compromise when only the submitting user viewed the record. Explain plausible consequences based on origin privileges and affected roles without overstating tested impact.
Recommend a context-specific fix. Use framework text rendering for plain text, a maintained sanitizer for allowed rich HTML, parsed URL allowlists for links, and removal of inline JavaScript contexts. Add CSP and Trusted Types as defense in depth. Avoid global input stripping because it damages legitimate data and misses alternate encodings.
Retest the exact source-sink path, then sibling components using the same helper. Verify the marker remains literal or is removed according to policy, legitimate content still renders, CSP stays enforced, and no alternate role or renderer executes it. For stored defects, remove old unsafe records or sanitize them during migration.
Pair browser evidence with the Playwright locator strategy guide when building maintainable UI assertions. Prefer accessible user-facing locators for workflow steps and targeted DOM locators only for the security oracle.
Interview Questions and Answers
Q: What is XSS?
XSS occurs when untrusted data is interpreted as executable browser content under a trusted origin. I describe it as a source-to-sink and context problem. Proof requires controlled execution, not merely seeing the input reflected.
Q: What are reflected, stored, and DOM-based XSS?
Reflected XSS returns request input in the immediate response. Stored XSS persists input and executes when another view renders it. DOM-based XSS occurs when client code moves untrusted data into an unsafe browser sink, sometimes without unsafe server HTML.
Q: How do you test XSS safely?
I begin with a unique inert marker and identify the exact output context. In an authorized isolated environment, I use a harmless marker that sets a window property and performs no exfiltration. I clean stored records immediately and inspect every affected renderer.
Q: Why is output encoding context-specific?
Browsers use different parsers for HTML, attributes, URLs, JavaScript, and CSS. A transformation safe for HTML text may be unsafe in a script or URL. Some dangerous contexts should not contain untrusted data at all.
Q: Does React prevent XSS?
React escapes normal text interpolation, which removes many common paths. XSS can still enter through dangerouslySetInnerHTML, unsafe URLs, direct DOM APIs, third-party components, or server-generated markup. I test escape hatches and actual sinks.
Q: What is the role of CSP?
CSP limits which scripts and resources a browser can execute and can report violations. It is defense in depth, not a substitute for safe output handling. I retest both the unsafe sink and policy enforcement.
Q: How do you automate stored XSS testing?
I create a uniquely marked record through the producer API, open every consumer role and renderer in Playwright, and observe execution markers, dialogs, errors, DOM, and network. Cleanup runs even after failure. The test tenant cannot notify real users.
Q: How do you report XSS impact accurately?
I state the execution origin, affected viewer, source, sink, and capability actually demonstrated. Then I explain realistic actions available in that origin without claiming untested credential theft or privilege. This keeps severity evidence-based.
Common Mistakes
- Declaring XSS because text is reflected without proving browser execution.
- Reusing one
<script>string across HTML, attribute, URL, JavaScript, CSS, and DOM contexts. - Using
alert()or exfiltration code in shared environments. - Checking raw HTML while missing the live DOM and client-side sinks.
- Assuming framework interpolation protects raw HTML escape hatches and unsafe URLs.
- Treating CSP, HttpOnly, input validation, or a WAF as the root-cause fix.
- Sanitizing on one renderer while admin, email, PDF, or mobile views remain unsafe.
- Leaving stored probes in shared records or privileged queues.
- Permanently HTML-encoding data in storage, which creates double encoding and fails other contexts.
- Forgetting filenames, error messages, imported data, Markdown, SVG, GraphQL, and
postMessage.
Conclusion
Testing for XSS requires context, a real browser, and disciplined evidence. Trace untrusted values from source through transformations to every sink, start with inert markers, use harmless execution proof only where authorized, and verify the correct encoding, URL policy, or sanitizer at the final renderer.
Begin with one feature that stores user text and is viewed by another role. Map every rendering context, automate a window marker oracle in Playwright, and add unit coverage around the exact encoder or sanitizer. Then expand the same source-to-sink method across errors, rich text, files, APIs, admin tools, and alternate clients.
Interview Questions and Answers
What is XSS?
XSS occurs when untrusted data becomes executable browser content under a trusted origin. I model the source, transformations, output context, and sink. Reflection alone is not proof of execution.
Explain reflected, stored, and DOM-based XSS.
Reflected XSS returns request input in the immediate response. Stored XSS persists input for later viewers. DOM-based XSS occurs when client code sends an untrusted source into an unsafe browser sink, sometimes even with a safe server response.
How do you test XSS without harming users?
I begin with inert unique markers and use a harmless `window` property only in an authorized isolated environment. I block external egress, avoid dialogs and exfiltration, and guarantee cleanup of stored records.
Why is encoding context-specific?
HTML, attributes, URLs, JavaScript, and CSS use different parsers and delimiters. HTML entity encoding may be correct for text but wrong for a script or URL. Some contexts should be redesigned to exclude untrusted data.
What dangerous DOM sinks do you review?
I look for `innerHTML`, `outerHTML`, `insertAdjacentHTML`, `document.write`, dynamic script creation, string timer arguments, and unsafe URL assignments. I trace inputs such as location, storage, messages, and API data to those sinks.
What is the role of sanitization?
Sanitization is appropriate when the product intentionally permits rich HTML. I use a maintained sanitizer with an explicit policy and test malformed markup, attributes, URL schemes, SVG, and every renderer. Plain text should use safe text rendering instead.
Why does CSP not close the root cause?
CSP can block execution and provide telemetry, but the unsafe sink may still enable HTML injection, navigation, or execution on a weaker page. I fix the data handling and verify CSP as defense in depth.
How do you verify an XSS remediation?
I rerun the exact source-sink path in a real browser, verify the marker is text or removed by policy, and confirm legitimate content still works. Then I test sibling renderers, stored legacy data, CSP, and sanitizer upgrade behavior.
Frequently Asked Questions
What is the first step in testing for XSS?
Submit a unique inert marker and locate it in the raw response, live DOM, attributes, scripts, storage, and later views. Identify the exact parser context before choosing any execution probe.
Does reflected input automatically mean XSS?
No. Reflection shows a data path, but execution depends on output context, encoding, browser parsing, sanitization, and policy. A valid finding needs controlled execution or clear unsafe-sink evidence.
How can QA test XSS safely?
Use an authorized isolated tenant and a harmless marker that only sets a unique `window` property. Do not exfiltrate data, capture credentials, create persistence outside the test record, or leave probes in shared queues.
What is the difference between stored and DOM-based XSS?
Stored XSS persists the value before a later render. DOM-based XSS describes client JavaScript moving untrusted data into an unsafe browser sink. One defect can be both when stored API data reaches `innerHTML`.
Does Content Security Policy prevent XSS?
A strong CSP blocks many execution paths and reports violations, but it does not repair the unsafe source-to-sink flow. Fix output handling and retain CSP as a second barrier.
Does React automatically prevent all XSS?
No. React escapes normal text interpolation, but raw HTML, unsafe URL handling, direct DOM APIs, server markup, and third-party components can reintroduce risk. Test every rendering escape hatch.
How do you automate stored XSS testing?
Create a uniquely marked record, visit every consumer role and renderer in Playwright, and observe execution, DOM, errors, CSP, and network. Delete the fixture through a trusted cleanup path even after failure.