3e5e135b47
- 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
100 lines
2.5 KiB
TypeScript
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> = {
|
|
"&": "&",
|
|
"<": "<",
|
|
">": ">",
|
|
'"': """,
|
|
"'": "'",
|
|
"/": "/",
|
|
};
|
|
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;
|
|
}
|