Files
AmirReza Jamali 3e5e135b47 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
2025-11-30 11:14:42 +03:30

100 lines
2.5 KiB
TypeScript

/**
* Basic HTML entity encoding for server-side sanitization
* Escapes dangerous characters to prevent XSS
*/
function escapeHtml(text: string): string {
const map: Record<string, string> = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"/": "&#x2F;",
};
return text.replace(/[&<>"'/]/g, (char) => map[char]);
}
/**
* Sanitizes HTML content to prevent XSS attacks
* @param dirty - The potentially unsafe HTML string
* @param allowedTags - Optional array of allowed HTML tags (only works client-side)
* @returns Sanitized HTML string safe for rendering
*/
export function sanitizeHtml(
dirty: string | undefined | null,
allowedTags?: string[]
): string {
if (!dirty) return "";
// Server-side: escape all HTML
if (typeof window === "undefined") {
return escapeHtml(dirty);
}
// Client-side: use DOMPurify
const DOMPurify = require("dompurify");
const config = {
ALLOWED_TAGS: allowedTags || ["b", "i", "em", "strong", "a", "br", "p"],
ALLOWED_ATTR: ["href", "target", "rel"],
ALLOW_DATA_ATTR: false,
};
return DOMPurify.sanitize(dirty, config);
}
/**
* Sanitizes plain text by stripping all HTML tags
* Use this for text that should never contain HTML
* @param dirty - The potentially unsafe string
* @returns Plain text with all HTML removed
*/
export function sanitizeText(dirty: string | undefined | null): string {
if (!dirty) return "";
// Server-side: escape HTML entities
if (typeof window === "undefined") {
return escapeHtml(dirty);
}
// Client-side: use DOMPurify
const DOMPurify = require("dompurify");
return DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: [],
ALLOWED_ATTR: [],
});
}
/**
* Sanitizes a URL to prevent javascript: and data: URI XSS attacks
* @param url - The potentially unsafe URL
* @returns Sanitized URL or empty string if invalid
*/
export function sanitizeUrl(url: string | undefined | null): string {
if (!url) return "";
// Check for dangerous protocols first
const lowerUrl = url.toLowerCase().trim();
if (
lowerUrl.startsWith("javascript:") ||
lowerUrl.startsWith("data:") ||
lowerUrl.startsWith("vbscript:") ||
lowerUrl.includes("<script")
) {
return "";
}
// Server-side: basic validation and escaping
if (typeof window === "undefined") {
return escapeHtml(url);
}
// Client-side: use DOMPurify
const DOMPurify = require("dompurify");
const sanitized = DOMPurify.sanitize(url, {
ALLOWED_TAGS: [],
ALLOWED_ATTR: [],
});
return sanitized;
}