| name | dom-security-hardening |
| description | When hardening a web application against Cross-Site Scripting (XSS) and injection attacks. |
| version | 2.0.0 |
| category | frontend |
| tags | ["frontend","security","xss","csp"] |
| skill_type | security |
| author | skiLLM |
| license | MIT |
| compatible_agents | ["claude-code","cursor","copilot","codex"] |
| estimated_context_tokens | 2200 |
| dangerous | false |
| requires_review | true |
| security_level | dangerous |
| dependencies | ["react-component-design"] |
| triggers | ["xss","injection","dom manipulation","csp","security","innerHTML","dangerouslySetInnerHTML"] |
| permissions | {"filesystem":{"read":true,"write":true},"network":{"outbound":false},"shell":{"execute":false}} |
| input_requirements | ["HTML codebase","JavaScript with DOM manipulation"] |
| output_contract | ["no inline scripts","no inline styles","csp enforced","no unsafe dom apis"] |
| failure_conditions | ["required sanitizer library unavailable","csp cannot be enforced","legacy framework requires unsafe-inline"] |
| last_updated | "2026-05-15T00:00:00.000Z" |
DOM Security Hardening
Purpose
XSS attacks kill applications. This skill hardens the DOM attack surface by enforcing a strict Content Security Policy, eliminating unsafe DOM APIs, and stripping execution vectors like inline scripts and styles. Following this skill is MANDATORY for any user-facing web application handling user input.
When to use
- Setting up the initial
index.html or document root of a web application
- Refactoring legacy code that relies on direct DOM manipulation
- Auditing a frontend codebase for XSS vulnerabilities
- Processing rich text or markdown input from untrusted users
- Creating forms or chat systems that accept user input
When NOT to use
- CSS framework design (separate concern)
- React component prop validation (use React Component Design)
- Backend input validation (different layer, still required)
Inputs required
- HTML document structure
- JavaScript files with DOM manipulation
- Understanding of Content Security Policy basics
- Sanitizer library decision (DOMPurify, sanitize-html, etc.)
Workflow
- Enforce CSP: Add strict
<meta http-equiv="Content-Security-Policy"> tag in HTML <head> (or HTTP headers)
- Externalize Assets: Extract ALL inline
<script> and <style> blocks into separate .js and .css files
- Remove Inline Events: Replace inline
onclick="...", onchange="..." with standard addEventListener bindings
- Replace Unsafe DOM: Replace
innerHTML, outerHTML, dangerouslySetInnerHTML with textContent or innerText
- Implement Sanitizer: If HTML rendering is REQUIRED, use DOMPurify or equivalent BEFORE insertion
- Test CSP: Verify policy blocks all unauthorized execution attempts
- Verify No Bypasses: Use security scanner (OWASP ZAP, Burp) to confirm XSS vectors are closed
Rules
- MUST implement strict CSP:
default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'
- MUST NEVER use inline
<script> blocks in HTML
- MUST NEVER use inline
style="..." attributes
- MUST NEVER use
eval(), setTimeout(string), new Function(string)
- MUST NEVER use
innerHTML, outerHTML, or dangerouslySetInnerHTML with user input
- MUST use
textContent or innerText for all dynamic text insertions
- MUST sanitize user HTML with DOMPurify before any insertion
- MUST NOT use
'unsafe-inline' or 'unsafe-eval' in CSP
Anti-patterns
innerHTML Assignment: element.innerHTML = userInput (immediate XSS)
javascript: URIs: href="javascript:void(0)" or href="javascript:alert(1)"
- Unsafe CSP:
Content-Security-Policy: default-src *; script-src 'unsafe-inline'
- Event Handler Strings: Creating event handlers from user input or strings
- Trusting User Input: Assuming any user input is safe to insert into DOM
- Missing Sanitizer: Using rich text editor without sanitizing output
Failure conditions
- CSP header/meta tag is missing
- Inline scripts or styles remain in production code
innerHTML used with user input
dangerouslySetInnerHTML used without sanitization
- CSP allows
'unsafe-inline' or 'unsafe-eval'
- Sanitizer library is not installed for rich text rendering
Validation checklist
Output format
- HTML file: Strict CSP meta tag in
<head>, external <script> tags at end of <body>
- JavaScript: All DOM mutations via safe APIs (
textContent, className, setAttribute)
- CSS: Separate
.css files, no inline styles anywhere
- Rich Text: HTML sanitized via DOMPurify before insertion
- Validation: Automated XSS scan passes
Security considerations
- Threat Model: Prevent XSS attacks via user input injection, DOM gadgets, third-party scripts
- Mitigations: CSP prevents execution, sanitizer prevents HTML injection, safe APIs prevent eval
- Constraints: CSP may conflict with analytics/ads (allow specific domains only)
- Legacy Code: Some frameworks may require CSP relaxation (document trade-offs)
- Third-party Scripts: Load analytics/ads only from trusted CDNs with subresource integrity (SRI)
Agent execution notes
- Agent MAY: Add CSP header, externalize inline scripts/styles, replace innerHTML with textContent, implement DOMPurify
- Agent MUST NEVER: Use
'unsafe-inline' or 'unsafe-eval' in CSP, ignore sanitization requirements, leave inline event handlers
- Agent MUST ASK: Before adding third-party scripts, before relaxing CSP for legacy code, before using dangerouslySetInnerHTML
- Agent MUST VALIDATE: CSP policy is strict, no inline scripts remain, no unsafe DOM APIs, XSS scanner passes
Example
❌ Anti-pattern (Unsafe XSS vectors, no CSP):
<a href="javascript:void(0)" onclick="submitForm()">Click me</a>
<div id="user-bio"></div>
<script>
document.getElementById('user-bio').innerHTML = getUserInput();
document.getElementById('user-bio').setAttribute('style', 'color: red;');
setTimeout("console.log('vulnerable')", 1000);
</script>
✅ Correct pattern (Hardened, safe):
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; img-src 'self' https:; font-src 'self';"
>
<a href="#" id="submit-btn">Click me</a>
<div id="user-bio"></div>
<script src="/js/app.js"></script>
document.getElementById('submit-btn').addEventListener('click', (e) => {
e.preventDefault();
submitForm();
});
document.getElementById('user-bio').textContent = getUserInput();
document.getElementById('user-bio').classList.add('text-red');
import DOMPurify from 'dompurify';
document.getElementById('user-bio').innerHTML = DOMPurify.sanitize(getUserMarkdown());