HTTP

Header

X-XSS-Protection Header

Deprecated header that enabled browser XSS filters to detect and block reflected cross-site scripting attacks.

6 min read beginner Try in Playground

TL;DR: Deprecated header that controlled browser XSS filters. Don’t use it—set to 0 to disable legacy filters and use Content-Security-Policy for XSS protection instead.

What is X-XSS-Protection?

The X-XSS-Protection header was a security feature that enabled built-in cross-site scripting (XSS) filters in older browsers. It instructed browsers to detect and block pages when reflected XSS attacks were detected.

Important: This header is now deprecated and should not be used. Modern browsers have removed XSS auditors due to security issues, and Content-Security-Policy (CSP) is the recommended approach for XSS protection.

How X-XSS-Protection Worked

Enable XSS filter:

HTTP/1.1 200 OK
Content-Type: text/html
X-XSS-Protection: 1; mode=block

<!DOCTYPE html>
<html>...
```http

Browser would block page if XSS was detected.

## Syntax

```http
X-XSS-Protection: 0
X-XSS-Protection: 1
X-XSS-Protection: 1; mode=block
X-XSS-Protection: 1; report=<reporting-uri>

Directives

  • 0 - Disable XSS filter
  • 1 - Enable XSS filter (sanitize page)
  • 1; mode=block - Enable filter and block page entirely
  • 1; report= - Enable filter and report violation (Chromium only)

Historical Usage

Disable Filter

X-XSS-Protection: 0
```text

Turned off XSS filtering (useful when it caused false positives).

### Enable with Sanitization

```http
X-XSS-Protection: 1

Browser would attempt to sanitize malicious code.

Enable with Blocking

X-XSS-Protection: 1; mode=block
```text

Browser would block the entire page instead of sanitizing.

### Enable with Reporting

```http
X-XSS-Protection: 1; mode=block; report=/xss-report

Block page and send violation report (Chromium-only feature).

Why It’s Deprecated

1. Created New Vulnerabilities

XSS auditors themselves introduced security vulnerabilities by allowing attackers to:

  • Selectively disable legitimate scripts
  • Create new attack vectors
  • Bypass security measures

2. False Positives

XSS filters frequently blocked legitimate content:

// Legitimate code that might trigger filter
const url = window.location.href
document.write('<div>' + url + '</div>')
```text

### 3. Inconsistent Behavior

Different browsers implemented XSS filters differently, causing:

- Unpredictable behavior
- Difficult debugging
- Cross-browser compatibility issues

### 4. Better Alternative Exists

Content-Security-Policy (CSP) provides:

- More granular control
- Better protection
- No false positive issues
- Active development and support

## Browser Support Timeline

### Chrome/Chromium

```text
Chrome 78+ (2019): XSS Auditor removed
Chrome 4-77: Supported
```text

### Firefox

```http
Firefox: Never implemented XSS Auditor
```text

### Safari

```http
Safari: Disabled by default in recent versions
```text

### Edge

```text
Legacy Edge: Supported
Chromium Edge 79+: XSS Auditor removed
```text

### Internet Explorer

```text
IE 8-11: Supported
```text

## Migration to CSP

### Old Approach (Deprecated)

```http
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; object-src 'none'
```text

CSP provides comprehensive XSS protection without the issues of XSS auditors.

## Server Configuration

### What NOT to Do Anymore

```nginx
# ❌ Don't add X-XSS-Protection header
add_header X-XSS-Protection "1; mode=block";

What to Do Instead

# ✅ Use Content-Security-Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'";

# Optionally include X-XSS-Protection: 0 to ensure it's disabled
add_header X-XSS-Protection "0";
```javascript

## Removing X-XSS-Protection

### Node.js (Express)

```javascript
const express = require('express')
const helmet = require('helmet')

const app = express()

// Modern approach with helmet
app.use(
  helmet({
    // Helmet v5+ doesn't set X-XSS-Protection by default
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'"],
        objectSrc: ["'none'"]
      }
    }
  })
)

// If using older helmet, explicitly disable
app.use((req, res, next) => {
  res.removeHeader('X-XSS-Protection')
  next()
})

app.listen(3000)

Legacy Code Cleanup

// ❌ Old code - remove this
app.use((req, res, next) => {
  res.set('X-XSS-Protection', '1; mode=block')
  next()
})

// ✅ Replace with CSP
app.use((req, res, next) => {
  res.set('Content-Security-Policy', "default-src 'self'; script-src 'self'")
  next()
})
```text

### Nginx

```nginx
# ❌ Remove old configuration
# add_header X-XSS-Protection "1; mode=block";

# ✅ Use CSP instead
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "DENY";

Apache

# ❌ Remove old configuration
# Header set X-XSS-Protection "1; mode=block"

# ✅ Use CSP instead
Header set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'"
Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "DENY"
```text

## Modern XSS Protection

### 1. Content-Security-Policy (Primary Defense)

```http
Content-Security-Policy:
    default-src 'self';
    script-src 'self' 'nonce-{random}';
    style-src 'self' 'nonce-{random}';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none'

2. Input Validation and Sanitization

// Validate and sanitize user input
const sanitizeHtml = require('sanitize-html')

app.post('/comment', (req, res) => {
  const clean = sanitizeHtml(req.body.comment, {
    allowedTags: ['b', 'i', 'em', 'strong'],
    allowedAttributes: {}
  })

  db.saveComment(clean)
  res.json({ success: true })
})
```javascript

### 3. Output Encoding

```javascript
// Use template engines with auto-escaping
const handlebars = require('handlebars')

// Handlebars auto-escapes by default
const template = handlebars.compile('<div>{{userInput}}</div>')
const output = template({ userInput: '<script>alert("XSS")</script>' })
// Output: <div>&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;</div>

4. HttpOnly Cookies

// Prevent JavaScript access to cookies
res.cookie('session', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict'
})
```text

### 5. Trusted Types

```http
Content-Security-Policy: require-trusted-types-for 'script'
// Use Trusted Types API
if (window.trustedTypes && trustedTypes.createPolicy) {
  const policy = trustedTypes.createPolicy('default', {
    createHTML: (string) => sanitizeHtml(string)
  })

  element.innerHTML = policy.createHTML(userInput)
}
```javascript

## Complete Security Headers Setup

### Modern Security Configuration

```javascript
const helmet = require('helmet')

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'", "'nonce-{random}'"],
        styleSrc: ["'self'", "'nonce-{random}'"],
        imgSrc: ["'self'", 'data:', 'https:'],
        objectSrc: ["'none'"],
        baseUri: ["'self'"],
        formAction: ["'self'"],
        frameAncestors: ["'none'"],
        upgradeInsecureRequests: []
      }
    },
    hsts: {
      maxAge: 31536000,
      includeSubDomains: true,
      preload: true
    },
    frameguard: {
      action: 'deny'
    },
    noSniff: true,
    referrerPolicy: {
      policy: 'strict-origin-when-cross-origin'
    }
  })
)

Nginx Configuration

# Modern security headers
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Explicitly disable XSS Protection (optional)
add_header X-XSS-Protection "0" always;
```text

## Testing and Validation

### Check Headers

```bash
# Check current headers
curl -I https://example.com

# Should NOT see X-XSS-Protection (or see it set to 0)
# SHOULD see Content-Security-Policy

Security Scanners

# Use online tools
# - https://securityheaders.com
# - https://observatory.mozilla.org

# Or CLI tools
npm install -g observatory-cli
observatory example.com
```javascript

### Browser DevTools

```javascript
// Check CSP in Console
console.log(document.querySelector("meta[http-equiv='Content-Security-Policy']"))

// Or check response headers in Network tab
fetch('/api/data').then((res) => {
  console.log('CSP:', res.headers.get('Content-Security-Policy'))
  console.log('X-XSS-Protection:', res.headers.get('X-XSS-Protection') || 'Not set')
})

Best Practices

1. Remove X-XSS-Protection

// ✅ Don't set it at all, or explicitly disable
res.set('X-XSS-Protection', '0')

// ❌ Don't enable it
res.set('X-XSS-Protection', '1; mode=block')
```text

### 2. Implement CSP

```http
# ✅ Comprehensive CSP
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123'

# ❌ Relying on deprecated header
X-XSS-Protection: 1; mode=block

3. Use Multiple Security Layers

// Defense in depth
app.use(helmet()) // Security headers
app.use(sanitizeInput) // Input sanitization
app.use(csurf()) // CSRF protection
app.use(rateLimit()) // Rate limiting
```text

### 4. Keep Dependencies Updated

```bash
# Regular security audits
npm audit
npm audit fix

# Update security packages
npm update helmet sanitize-html

5. Educate Developers

// Document why X-XSS-Protection is not used
/**
 * Security Headers
 *
 * We do NOT use X-XSS-Protection because:
 * - It's deprecated and removed from modern browsers
 * - It created new vulnerabilities
 * - Content-Security-Policy is the modern replacement
 *
 * Instead, we use:
 * - Content-Security-Policy for XSS protection
 * - Input validation and sanitization
 * - Output encoding
 * - HttpOnly cookies
 */
```text

## Common Misconceptions

### Myth 1: "Still Needed for Old Browsers"

**Reality:** Old browsers with XSS auditors have other security issues. Better to use CSP which degrades gracefully.

### Myth 2: "Setting it Doesn't Hurt"

**Reality:** XSS auditors can create vulnerabilities. If present in older browsers, explicitly disable with `X-XSS-Protection: 0`.

### Myth 3: "CSP is Too Complex"

**Reality:** Start with a simple CSP and iterate:

```http
# Simple starting point
Content-Security-Policy: default-src 'self'; script-src 'self'

Frequently Asked Questions

What is X-XSS-Protection?

X-XSS-Protection controlled browser XSS filters. It is now deprecated because the filters could be exploited and CSP provides better protection.

Should I still use X-XSS-Protection?

Set X-XSS-Protection: 0 to disable legacy XSS filters that can cause issues. Use Content-Security-Policy instead for XSS protection.

Why was X-XSS-Protection deprecated?

The XSS filters had bypasses and could be exploited to disable legitimate scripts. Modern browsers removed the feature. CSP is the recommended replacement.

What should I use instead?

Use Content-Security-Policy with strict script-src directives. CSP provides comprehensive XSS protection without the vulnerabilities of X-XSS-Protection.

Keep Learning