Do not rely on denylists to stop cross-site scripting. Use context-aware output encoding, HTML sanitisation where needed, and allowlists for any permitted markup or attributes. Validate data on input, but also encode on output for the exact browser context. Test with scriptable payloads, event handlers and encoded variants, because incomplete blocking is easy to evade.
Why denylist-only defences fail against XSS
An incomplete denylist is fragile because XSS payloads have many equivalent forms, and browser parsing is forgiving. If you block only a few keywords or patterns, an attacker can often change the syntax, encode the payload, move it into a different HTML context, or use an event handler or alternate browser construct that still executes.
The underlying issue is that XSS is a context problem, not just a bad-string problem. A filter that seems effective in one place can fail in HTML text, attributes, JavaScript, URL-valued attributes, or templated markup, because each context needs a different output handling rule.
Controls that reduce XSS risk more reliably
The most reliable mitigation is context-aware output encoding, because the browser interprets data according to where it lands. Encode for the exact sink and browser context, not just generically, so untrusted data is treated as data rather than markup or script.
When the application genuinely needs to accept limited rich text or markup, use HTML sanitisation with a strict allowlist of permitted elements and attributes. Keep the allowlist narrow, and remove anything that can create script execution, event binding, or dangerous URL handling.
Input validation still matters, but it should be treated as a quality and data-shaping control, not the sole XSS defence. Validate early, then still encode on output, because the same value may later be rendered in a different context than the one you originally expected.
- Use output encoding that matches the destination context, such as HTML body, HTML attribute, JavaScript string, or URL.
- Prefer allowlists over denylists for any permitted HTML, attributes, or formatting.
- Reject or strip dangerous constructs such as scriptable attributes, inline event handlers, and unsafe URL schemes.
- Apply sanitisation before rendering user-controlled markup, not after it has already reached the browser.
How to test whether the defence really holds
A denylist often looks adequate until it meets a payload variant the developer did not anticipate. Test with scriptable payloads, event-handler variants, nested encodings, and browser-specific edge cases so you can see whether the application blocks execution or merely blocks one spelling of it.
Good testing should also cover the places people forget: error pages, template fragments, search results, rich-text fields, and any feature that reflects user input back into HTML. If a value can be displayed, stored, or reused in another view, it should be tested in every relevant output context.
Sanity-check the actual rendered page, not only the server response body. A response that looks harmless in raw text can become dangerous once the browser decodes entities, interprets attributes, or merges injected markup into the DOM.
Risk and Threat Considerations
Incomplete denylists create a false sense of coverage, because attackers only need one unblocked variant to recover execution. The practical risk is account compromise, session theft, malicious action in the victim’s browser, and trust abuse in any application that renders untrusted content.
Failure mechanism: The application blocks a small set of known bad strings, but the attacker changes encoding, context, or browser syntax so the payload survives filtering and executes in the client.
Impact: Successful XSS can expose data, hijack sessions, perform actions as the user, inject misleading content, or pivot into broader compromise if privileged users trigger the payload.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
OWASP API Security Top 10 addresses the attack and risk surface, while OWASP ASVS, CIS Controls v8 and NIST SP 800-53 Rev 5 set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| OWASP ASVS | V1 — Encoding and Sanitization | XSS prevention depends on correct encoding and sanitization. |
| V7 — Session Management | XSS often targets session theft and browser-side abuse. | |
| V16 — Security Logging and Error Handling | XSS testing and detection rely on observable errors and abuse signals. | |
| Recommendation — Apply V1 to encode untrusted data and sanitize any permitted markup before rendering. Harden session handling so stolen browser state is less useful after XSS. Log suspicious input and rendering anomalies to support XSS detection and triage. | ||
| CIS Controls v8 | CIS-16 — Application Software Security | XSS is an application security flaw requiring secure coding and validation. |
| Recommendation — Build secure coding checks that enforce output encoding and approved sanitization. | ||
| NIST SP 800-53 Rev 5 | SI-10 — Information Input Validation | Input validation helps shape untrusted data before rendering. |
| Recommendation — Validate inputs early, then still protect every output sink with context-specific encoding. | ||
| OWASP API Security Top 10 | API3 — Broken Object Property Level Authorization | User-controlled data reflected into APIs or clients can create unsafe exposure patterns. |
| Recommendation — Restrict exposed fields so untrusted values cannot flow into dangerous render paths. | ||
Practitioner Guidance
What to verify: Confirm that every untrusted value is encoded at the point of output for the exact browser context, and that any HTML sanitiser is configured as a narrow allowlist rather than a broad blocklist. If the same field is reused in more than one template or context, verify each sink separately.
Common mistake: Teams often add a denylist after finding one exploit and assume the page is now safe. That approach fails when the payload is transformed, double-encoded, stored for later rendering, or moved into a different context by a template change.
Practitioner takeaway: Treat denylists as a brittle backstop, not a primary control, and assume the browser will eventually find a way to interpret any untrusted string unless output encoding and sanitisation are built into the rendering path.
Related resources from NHI Mgmt Group
- Guide: Web Application Attacks Guide
- How do security teams reduce stored cross-site scripting risk in browser-rendered inventory notes and comments?
- Why does Content Security Policy reduce cross-site scripting and injection risk in web applications?
- Why do secure defaults reduce cross-site scripting risk in large codebases?
- How should Spring teams implement Content Security Policy to reduce cross-site scripting risk in web applications?