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:
@@ -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: <script>alert('XSS')</script>
|
||||
|
||||
// 2. Event handler injection
|
||||
const xss2 = "<img src=x onerror=alert('XSS')>";
|
||||
sanitizeText(xss2); // Safe: <img src=x onerror=alert('XSS')>
|
||||
|
||||
// 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: <iframe src='javascript:alert(1)'></iframe>
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
Reference in New Issue
Block a user