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,61 @@
|
||||
import { sanitizeHtml, sanitizeText, sanitizeUrl } from "../sanitize";
|
||||
|
||||
describe("Sanitization Functions", () => {
|
||||
describe("sanitizeText", () => {
|
||||
it("should remove HTML tags", () => {
|
||||
const dirty = "<script>alert('XSS')</script>Hello";
|
||||
const clean = sanitizeText(dirty);
|
||||
expect(clean).not.toContain("<script>");
|
||||
expect(clean).not.toContain("</script>");
|
||||
});
|
||||
|
||||
it("should handle null and undefined", () => {
|
||||
expect(sanitizeText(null)).toBe("");
|
||||
expect(sanitizeText(undefined)).toBe("");
|
||||
});
|
||||
|
||||
it("should escape dangerous characters", () => {
|
||||
const dirty = "<img src=x onerror=alert('XSS')>";
|
||||
const clean = sanitizeText(dirty);
|
||||
expect(clean).not.toContain("<img");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sanitizeUrl", () => {
|
||||
it("should block javascript: URLs", () => {
|
||||
const dirty = "javascript:alert('XSS')";
|
||||
const clean = sanitizeUrl(dirty);
|
||||
expect(clean).toBe("");
|
||||
});
|
||||
|
||||
it("should block data: URLs", () => {
|
||||
const dirty = "data:text/html,<script>alert('XSS')</script>";
|
||||
const clean = sanitizeUrl(dirty);
|
||||
expect(clean).toBe("");
|
||||
});
|
||||
|
||||
it("should allow safe URLs", () => {
|
||||
const safe = "https://example.com";
|
||||
const clean = sanitizeUrl(safe);
|
||||
expect(clean).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should handle null and undefined", () => {
|
||||
expect(sanitizeUrl(null)).toBe("");
|
||||
expect(sanitizeUrl(undefined)).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sanitizeHtml", () => {
|
||||
it("should remove script tags", () => {
|
||||
const dirty = "<p>Hello</p><script>alert('XSS')</script>";
|
||||
const clean = sanitizeHtml(dirty);
|
||||
expect(clean).not.toContain("<script>");
|
||||
});
|
||||
|
||||
it("should handle null and undefined", () => {
|
||||
expect(sanitizeHtml(null)).toBe("");
|
||||
expect(sanitizeHtml(undefined)).toBe("");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user