no-innerhtml
Detects dangerous innerHTML/outerHTML assignments that can lead to Cross-Site Scripting (XSS). This rule is part of [`eslint-plugin-frontend-security`](https://
Keywords: XSS, innerHTML, outerHTML, CWE-79, security, DOM manipulation, sanitization, DOMPurify
Detects dangerous innerHTML/outerHTML assignments that can lead to Cross-Site Scripting (XSS). This rule is part of eslint-plugin-frontend-security.
⚠️ This rule errors by default in the recommended config.
Quick Summary
| Aspect | Details |
|---|---|
| CWE Reference | CWE-79 (Cross-site Scripting) |
| Severity | 🔴 Critical |
| Auto-Fix | ✅ Yes (suggests DOMPurify) |
| Category | Security |
| Best For | Frontend apps, React/Vue/Angular, DOM manipulation |
Vulnerability and Risk
Vulnerability: Assigning unsanitized user input to innerHTML or outerHTML allows attackers to inject malicious scripts.
Risk: XSS attacks can:
- Steal session cookies and authentication tokens
- Perform actions as the victim user
- Redirect to phishing sites
- Install keyloggers
How XSS via innerHTML Works
Examples
❌ Incorrect
// Direct assignment of user input - CRITICAL XSS
element.innerHTML = userInput;
// Template literal with user data - VULNERABLE
element.innerHTML = `<div>${userData.name}</div>`;
// Function result without sanitization - VULNERABLE
element.innerHTML = getUserContent();
element.outerHTML = fetchedData;
// React dangerouslySetInnerHTML (different rule, same concept)
<div dangerouslySetInnerHTML={{ __html: userContent }} />;✅ Correct
// Use textContent for text - SAFE
element.textContent = userInput;
// Sanitize with DOMPurify - SAFE
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);
// Literal strings are safe
element.innerHTML = '<p>Static content</p>';
// Create elements programmatically - SAFE
const div = document.createElement('div');
div.textContent = userInput;
container.appendChild(div);Options
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | false | Allow innerHTML in test files |
trustedSanitizers | string[] | ['DOMPurify.sanitize', 'sanitize', 'sanitizeHtml', 'xss', 'purify'] | Trusted sanitizer function names |
allowLiteralStrings | boolean | true | Allow innerHTML with literal strings |
{
"rules": {
"frontend-security/no-innerhtml": [
"error",
{
"trustedSanitizers": ["DOMPurify.sanitize", "myCustomSanitizer"],
"allowLiteralStrings": true
}
]
}
}Best Practices
1. Use DOMPurify
import DOMPurify from 'dompurify';
function renderUserContent(html) {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
}
element.innerHTML = renderUserContent(userInput);2. Prefer textContent
// For plain text, always use textContent
element.textContent = userData.name; // Safe - no HTML parsing3. Use DOM APIs
// Build DOM programmatically
function createComment(user, text) {
const div = document.createElement('div');
div.className = 'comment';
const author = document.createElement('span');
author.textContent = user.name; // Safe
const content = document.createElement('p');
content.textContent = text; // Safe
div.appendChild(author);
div.appendChild(content);
return div;
}Related Rules
no-eval- Detects dangerous eval usageno-sensitive-localstorage- Detects sensitive data in localStorage
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Content from Variable
Why: Content assigned to variables is not traced.
// ❌ NOT DETECTED - Content from variable
const html = userInput;
element.innerHTML = html;Mitigation: Always sanitize before assignment to any variable.
Custom Sanitizer Not Recognized
Why: Non-standard sanitizer names may not be detected.
// ❌ NOT DETECTED - Custom sanitizer
element.innerHTML = myCustomEscape(userInput);Mitigation: Configure trustedSanitizers with custom function names.
Framework Bindings
Why: Framework-specific binding may not be recognized.
// ❌ NOT DETECTED - jQuery html()
$element.html(userInput);
// ❌ NOT DETECTED - Angular [innerHTML]
<div [innerHTML]="userContent"></div>Mitigation: Use framework-specific security rules.
Dynamic Property Assignment
Why: Dynamic property access is not analyzed.
// ❌ NOT DETECTED - Dynamic property
const prop = 'innerHTML';
element[prop] = userInput;Mitigation: Avoid dynamic property assignment for DOM manipulation.
Resources
Error Message Format
The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:
⚠️ CWE-79 OWASP:A05 CVSS:6.1 | Cross-site Scripting (XSS) detected | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001]
Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/Message Components
| Component | Purpose | Example |
|---|---|---|
| Risk Standards | Security benchmarks | CWE-79 OWASP:A05 CVSS:6.1 |
| Issue Description | Specific vulnerability | Cross-site Scripting (XSS) detected |
| Severity & Compliance | Impact assessment | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001] |
| Fix Instruction | Actionable remediation | Follow the remediation steps below |
| Technical Truth | Official reference | OWASP Top 10 |
browser-security/no-filereader-innerhtml
The rule provides **LLM-optimized error messages** (Compact 2-line format) with actionable security guidance:
browser-security/no-jwt-in-storage
This rule prevents storing JWT tokens in browser storage (localStorage/sessionStorage). JWTs stored in these locations are fully accessible to JavaScript, makin