No dynamic script injection with user-controlled content
Why it matters
Dynamic DOM injection — document.write(), innerHTML assignments with external content, and dynamically created <script> elements with user-controlled sources — creates XSS vulnerabilities that CSP cannot fully mitigate. An attacker who controls any input to these patterns achieves script execution in the user's browser context: session token theft, credential harvesting, or full account takeover. CWE-79 (XSS) and CWE-83 (Script in HTML Attribute) both apply directly. OWASP A03 (Injection) lists DOM-based XSS as a primary injection vector. These patterns persist in codebases long after their introduction because they appear to work correctly during normal operation — failures only surface under adversarial input.
Severity rationale
Critical because `innerHTML` with external content, `document.write()`, and dynamic script injection are direct XSS vectors that enable arbitrary script execution regardless of CSP configuration, since CSP cannot prevent patterns already executing in trusted script context.
Remediation
Replace DOM injection patterns with safe equivalents. Text content should use textContent; structured content should use the DOM API or framework rendering.
// DANGEROUS: XSS vector
element.innerHTML = apiResponse.html
document.write('<script src=' + userInput + '></script>')
// SAFE: text only
element.textContent = apiResponse.text
// SAFE: structured DOM construction
const p = document.createElement('p')
p.textContent = apiResponse.title
container.appendChild(p)
// SAFE: React auto-escapes
return <div>{apiResponse.content}</div>
// LAST RESORT: sanitized HTML (treat as tech debt)
import DOMPurify from 'dompurify'
element.innerHTML = DOMPurify.sanitize(apiResponse.html)
DOMPurify reduces risk but does not eliminate the injection pattern — flag every innerHTML assignment in code review regardless of sanitization.
Detection
-
ID:
no-dynamic-injection -
Severity:
critical -
What to look for: Search all JavaScript/TypeScript source files for:
document.write()calls — count all occurrencesinnerHTMLorouterHTMLassignments with external or user-controlled content — count all occurrences where the assigned value comes from user input, URL parameters, API responses, or any external source- Dynamically created
<script>elements with user-controlledsrcattributes — count occurrences ofcreateElement('script')or template literals building script tags
Report: "X document.write calls, Y innerHTML assignments with external content, Z dynamic script injections found."
-
Pass criteria: All counts are 0 — 0% of source files contain DOM injection patterns. Report the counts even on pass.
-
Fail criteria: Any count is greater than 0.
-
Do NOT pass when:
innerHTMLis used with DOMPurify sanitization — that is a mitigation, not an elimination. This check is specifically about DOM injection patterns, not sanitization quality. DOMPurify reduces risk but does not eliminate the attack surface. -
Skip (N/A) when: Never skip — this check applies to every project with JavaScript source files.
-
Cross-reference: For XSS prevention patterns beyond headers, the Client-Side Framework Security audit covers DOM sanitization.
-
Detail on fail:
"X document.write calls, Y innerHTML assignments with external content, Z dynamic script injections found"or"innerHTML used with API response data in UserProfile component — DOMPurify present but pattern is still flagged" -
Remediation: DOM injection patterns create XSS vulnerabilities regardless of sanitization. Replace them with safe alternatives:
// DANGEROUS: innerHTML with external content element.innerHTML = apiResponse.content // SAFE: textContent for text-only content element.textContent = apiResponse.content // SAFE: DOM API for structured content const p = document.createElement('p') p.textContent = apiResponse.title container.appendChild(p) // SAFE: React/framework rendering (auto-escapes) return <div>{apiResponse.content}</div>If you must render HTML, use your framework's built-in sanitization (React's
dangerouslySetInnerHTMLwith DOMPurify) and treat it as technical debt to eliminate.
External references
- cwe · CWE-79
- cwe · CWE-83 — Improper Neutralization of Script in Attributes in a Web Page
- owasp:2021 · A03
Taxons
History
- 2026-04-18·v1.0.0·Initial import from security-headers-ii·automated