feat(security): Add XSS protection with DOMPurify sanitization

- Add sanitize.ts utility module with sanitizeText(), sanitizeUrl(), and sanitizeHtml() functions
- Implement server-side HTML entity escaping and client-side DOMPurify sanitization
- Add comprehensive test suite for sanitization functions in lib/__tests__/sanitize.test.ts
- Secure marketing pages by sanitizing all CMS data (hero, features, challenges, advantages, footer)
- Secure inquiries form by sanitizing field labels, placeholders, and button text
- Add SECURITY.md documentation outlining XSS protection measures and best practices
- Add USAGE_EXAMPLES.md with detailed examples of sanitization function usage
- Add XSS_PROTECTION_SUMMARY.md with implementation overview
- Update package.json to include dompurify (^3.3.0) dependency
- Protects against common XSS attack vectors including script injection, event handlers, and malicious URLs
This commit is contained in:
AmirReza Jamali
2025-11-30 11:14:42 +03:30
parent 72b61aa6a0
commit 3e5e135b47
10 changed files with 691 additions and 33 deletions
+222
View File
@@ -0,0 +1,222 @@
# XSS Protection Usage Examples
## Basic Usage
### Sanitizing Text Content
```typescript
import { sanitizeText } from "@/lib/sanitize";
// In a component
function MyComponent({ cmsData }) {
return (
<div>
<h1>{sanitizeText(cmsData.title)}</h1>
<p>{sanitizeText(cmsData.description)}</p>
</div>
);
}
```
### Sanitizing URLs
```typescript
import { sanitizeUrl } from "@/lib/sanitize";
import Link from "next/link";
import Image from "next/image";
function MyComponent({ cmsData }) {
return (
<div>
{/* For links */}
<Link href={sanitizeUrl(cmsData.link) || "#"}>Click here</Link>
{/* For images */}
<Image
src={sanitizeUrl(cmsData.imageUrl) || "/fallback.svg"}
alt={sanitizeText(cmsData.imageAlt)}
width={100}
height={100}
/>
</div>
);
}
```
### Sanitizing HTML Content
```typescript
import { sanitizeHtml } from "@/lib/sanitize";
function MyComponent({ cmsData }) {
return (
<div>
{/* With default allowed tags (b, i, em, strong, a, br, p) */}
<div
dangerouslySetInnerHTML={{
__html: sanitizeHtml(cmsData.richContent),
}}
/>
{/* With custom allowed tags */}
<div
dangerouslySetInnerHTML={{
__html: sanitizeHtml(cmsData.richContent, ["p", "br", "strong"]),
}}
/>
</div>
);
}
```
## Real-World Examples
### Example 1: Dynamic Marketing Page
```typescript
import { sanitizeText, sanitizeUrl } from "@/lib/sanitize";
export default function MarketingPage({ cmsData }) {
return (
<main>
<section>
<h1>{sanitizeText(cmsData.hero.title)}</h1>
<p>{sanitizeText(cmsData.hero.description)}</p>
<Link href={sanitizeUrl(cmsData.hero.buttonLink) || "#"}>
{sanitizeText(cmsData.hero.buttonText)}
</Link>
</section>
<section>
{cmsData.features.map((feature) => (
<article key={feature.id}>
<Image
src={sanitizeUrl(feature.icon) || "/default-icon.svg"}
alt={sanitizeText(feature.title)}
width={48}
height={48}
/>
<h3>{sanitizeText(feature.title)}</h3>
<p>{sanitizeText(feature.description)}</p>
</article>
))}
</section>
</main>
);
}
```
### Example 2: Dynamic Form Fields
```typescript
import { sanitizeText } from "@/lib/sanitize";
import { useForm } from "react-hook-form";
export default function DynamicForm({ contactData }) {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
{contactData.fields.map((field) => (
<div key={field.name}>
<label htmlFor={field.name}>{sanitizeText(field.label)}</label>
<input
id={field.name}
placeholder={sanitizeText(field.placeholder)}
{...register(field.name)}
/>
</div>
))}
<button type="submit">
{sanitizeText(contactData.submitButtonText)}
</button>
</form>
);
}
```
### Example 3: User-Generated Content
```typescript
import { sanitizeHtml } from "@/lib/sanitize";
export default function CommentSection({ comments }) {
return (
<div>
{comments.map((comment) => (
<article key={comment.id}>
<h4>{sanitizeText(comment.author)}</h4>
{/* Allow only basic formatting tags */}
<div
dangerouslySetInnerHTML={{
__html: sanitizeHtml(comment.content, [
"p",
"br",
"em",
"strong",
]),
}}
/>
</article>
))}
</div>
);
}
```
## Common XSS Attack Vectors (All Blocked)
```typescript
// These malicious inputs will be sanitized:
// 1. Script injection
const xss1 = "<script>alert('XSS')</script>";
sanitizeText(xss1); // Safe: &lt;script&gt;alert('XSS')&lt;/script&gt;
// 2. Event handler injection
const xss2 = "<img src=x onerror=alert('XSS')>";
sanitizeText(xss2); // Safe: &lt;img src=x onerror=alert('XSS')&gt;
// 3. JavaScript URL
const xss3 = "javascript:alert('XSS')";
sanitizeUrl(xss3); // Returns: ""
// 4. Data URL with script
const xss4 = "data:text/html,<script>alert('XSS')</script>";
sanitizeUrl(xss4); // Returns: ""
// 5. Iframe injection
const xss5 = "<iframe src='javascript:alert(1)'></iframe>";
sanitizeText(xss5); // Safe: &lt;iframe src='javascript:alert(1)'&gt;&lt;/iframe&gt;
```
## Best Practices
1. **Always sanitize external data** before rendering
2. **Use the appropriate function** for the content type
3. **Provide fallback values** for URLs and optional content
4. **Never trust user input** or external API data
5. **Keep dependencies updated** to get latest security patches
## When NOT to Sanitize
- Static content defined in your codebase
- Environment variables (but validate them)
- Data from trusted internal APIs (but still validate)
- Constants and configuration values
## Testing Your Sanitization
```typescript
// Test with malicious input
const testData = {
title: "<script>alert('XSS')</script>Hello",
link: "javascript:alert('XSS')",
description: "<img src=x onerror=alert('XSS')>",
};
// All should be safe
console.log(sanitizeText(testData.title)); // No script execution
console.log(sanitizeUrl(testData.link)); // Empty string
console.log(sanitizeText(testData.description)); // No image/script
```