TL;DR: Systematically remediate 10 distinct XSS vulnerabilities by applying established patterns from the secure-react-app: sanitize all HTML with DOMPurify, validate all URLs, encode all text output via React's JSX, replace dangerouslySetInnerHTML with sanitized alternatives, remove unsafe DOM manipulation, eliminate eval(), and add comprehensive security headers/CSP.
- Install DOMPurify library (
npm install dompurify) - Create reusable sanitization utility functions following secure-react-app patterns
- Configure strict sanitization config:
- ALLOWED_TAGS whitelist:
['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'] - FORBID_TAGS blacklist:
['script', 'style', 'iframe', 'form', 'input'] - FORBID_ATTR blacklist:
['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus'] - ALLOW_DATA_ATTR:
false
- ALLOWED_TAGS whitelist:
- Create different sanitization levels for different content types (rich HTML for bio, plain text for comments)
Vulnerability 1 - Bio rendering in renderBio():
- Apply DOMPurify.sanitize() to bio before passing to dangerouslySetInnerHTML
- Use useMemo to memoize sanitization for performance
Vulnerability 4 - Comments rendering in renderComments():
- Change comments to plaintext (no HTML allowed)
- Remove dangerouslySetInnerHTML entirely
- Let React auto-escape the text content
- Sanitize comment input BEFORE storing (not just before rendering)
Vulnerability 6 - Error message rendering:
- Remove dangerouslySetInnerHTML from error display
- Use regular React text rendering (auto-escaped)
- Keep error message as plain text only
Vulnerability 9 - Dynamic button rendering:
- Remove renderDynamicButton() entirely OR
- If button action needed, sanitize to safe text content only
- Never pass user input to onclick or other event handlers via dangerouslySetInnerHTML
- Delete the useEffect hook that assigns innerHTML directly to bioRef
- Replace with React state management
- Let React handle all DOM updates declaratively
- Implement
isValidUrl()helper function:- Use native URL constructor to validate format
- Whitelist only
http:andhttps:protocols - Block
javascript:,data:, and malformed URLs
- Call validation in renderWebsiteLink() before rendering
<a>tag - Show error message if URL is invalid
Vulnerability 7 - calculateAge() function:
- Replace
eval(calculation)with simple arithmetic:return 2024 - birthYear - Never use eval() with user input
Vulnerability 8 - renderDynamicContent() function:
- Remove renderDynamicContent() entirely
- Never use document.write() with user content
- Use React state and JSX for dynamic content instead
- In the useEffect that reads URL search params:
- Apply DOMPurify.sanitize() to searchParam before storing
- Use ALLOWED_TAGS: [] to strip all HTML
- Store sanitized value in state
- Never directly inject URL parameters into innerHTML
- Create regex whitelist for CSS color values:
- Pattern:
^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$|^rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\)$
- Pattern:
- Validate backgroundColor input against regex
- Default to safe fallback
#ffffffif validation fails - Prevent CSS injection via background-image or other tricks
- Install helmet.js middleware for server
- Configure Content-Security-Policy headers:
script-src: ['self'](no inline scripts)object-src: ['none'](no plugins)frame-src: ['none'](no iframe embedding)- Other standard security headers (X-Content-Type-Options: nosniff, X-Frame-Options: DENY, etc.)
- Apply helmet middleware to all responses
- Update all
<a>tags withtarget="_blank"to include:rel="noopener noreferrer"
- In renderWebsiteLink():
<a href={website} target="_blank" rel="noopener noreferrer">
- Test each vulnerability class independently:
- Script tag injection (various encodings)
- Event handler injection (onerror, onload, onclick, onmouseover, etc.)
- JavaScript URL protocol bypass (javascript:, jAvAsCrIpT:, with null bytes)
- SVG-based XSS injection
- Style-based XSS (background-image with data URI)
- DOM-based XSS from URL parameters
- Stored XSS in comments
- Follow secure-react-app test patterns using @testing-library/react
- Run tests in CI/CD pipeline before merge
- Verify no regressions in functionality after fixes
| Vulnerability | Category | Fix | Priority |
|---|---|---|---|
| 1 - dangerouslySetInnerHTML bio | DOM XSS | Apply DOMPurify sanitization | CRITICAL |
| 2 - innerHTML via useEffect | DOM XSS | Remove useEffect, use React rendering | CRITICAL |
| 3 - javascript: href | DOM XSS | Add URL validation with isValidUrl() | CRITICAL |
| 4 - Stored XSS comments | Stored XSS | Remove HTML, plain text only | CRITICAL |
| 5 - URL search parameter | Reflected XSS | Sanitize URL params before state | HIGH |
| 6 - Error message reflection | Reflected XSS | Remove dangerouslySetInnerHTML, auto-escape | HIGH |
| 7 - eval() code injection | Code Injection | Replace with direct arithmetic | CRITICAL |
| 8 - document.write | DOM XSS | Remove entirely, use React state | HIGH |
| 9 - Event handler injection | DOM XSS | Remove dynamic onclick via dangerouslySetInnerHTML | HIGH |
| 10 - CSS injection | CSS Injection | Add regex whitelist validation | MEDIUM |
- Follow secure-react-app test patterns with @testing-library/react
- Test each vulnerability class independently (script injection, event handler, protocol bypass, etc.)
- Run tests in CI/CD pipeline before merge
- Use @testing-library/react utilities: render(), screen, userEvent
- Test both successful injection attempts (should be blocked) and normal use cases (should work)
- Fix CRITICAL vulnerabilities first (1, 2, 3, 4, 7)
- Validate each fix with security tests before proceeding
- Deploy to staging for QA before production
- Communicate changes to users (especially regarding comment HTML stripping)
- Monitor for issues before moving to next vulnerability class
- DOMPurify will strip dangerous HTML from existing bios
- Comments stored as plain text will no longer support HTML formatting
- Consider data migration strategy:
- Option A: Re-sanitize on load (one-time operation)
- Option B: Batch-clean database records before deployment
- Option C: Notify users that HTML is no longer supported and suggest re-entering content
- Preserve all valid data (DOMPurify removes tags but keeps text content)
{
"dompurify": "^3.0.6",
"helmet": "^7.1.0"
}- Use React.useMemo() for expensive sanitization operations
- Sanitize once on input, not repeatedly on output
- Consider debouncing user input for large textareas
Content-Security-Policy: script-src 'self'; object-src 'none'; frame-src 'none'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains
Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=()
- Review and refine this plan with stakeholders
- Create feature branch for XSS remediation work
- Implement fixes in priority order (CRITICAL vulnerabilities first)
- Write and run security tests as you go
- Get security review before merging
- Deploy to staging for QA validation
- Monitor production for issues post-deployment