From 77a2fa68c62b93d5ddfe66f429698f80d8a785ca Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Jan 2026 02:36:13 +0000 Subject: [PATCH] Fix XSS vulnerability by sanitizing HTML in blog posts Added DOMPurify library to sanitize HTML content before rendering with dangerouslySetInnerHTML. This prevents Cross-Site Scripting (XSS) attacks where malicious scripts could be injected through blog post content. Changes: - Installed dompurify and @types/dompurify - Added HTML sanitization in BlogPost.tsx (client/pages/BlogPost.tsx:139) - Added HTML sanitization in AdminBlogEditor.tsx preview (client/components/admin/AdminBlogEditor.tsx:273) Security impact: HIGH - Previously, unsanitized HTML from the API could execute arbitrary JavaScript, potentially stealing user credentials or performing unauthorized actions. --- client/components/admin/AdminBlogEditor.tsx | 3 ++- client/pages/BlogPost.tsx | 3 ++- package-lock.json | 28 +++++++++++++++++++++ package.json | 2 ++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/client/components/admin/AdminBlogEditor.tsx b/client/components/admin/AdminBlogEditor.tsx index 21d46c79..a0ca82d2 100644 --- a/client/components/admin/AdminBlogEditor.tsx +++ b/client/components/admin/AdminBlogEditor.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import DOMPurify from "dompurify"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; @@ -269,7 +270,7 @@ const BlogEditor = ({ onPublish, initialData }: BlogEditorProps) => { {excerpt && (

{excerpt}

)} -
+
)} diff --git a/client/pages/BlogPost.tsx b/client/pages/BlogPost.tsx index c8d8d382..770de28f 100644 --- a/client/pages/BlogPost.tsx +++ b/client/pages/BlogPost.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import { useParams, Link } from "react-router-dom"; +import DOMPurify from "dompurify"; import Layout from "@/components/Layout"; import SEO from "@/components/SEO"; import { @@ -135,7 +136,7 @@ export default function BlogPost() { {post.body ? ( -
+
) : (

{post.excerpt}

)} diff --git a/package-lock.json b/package-lock.json index 00c536af..353357dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@vercel/analytics": "^1.5.0", "adm-zip": "^0.5.16", "chokidar": "^3.6.0", + "dompurify": "^3.3.1", "dotenv": "^17.2.0", "ethers": "^6.13.0", "express": "^4.18.2", @@ -59,6 +60,7 @@ "@tailwindcss/typography": "^0.5.15", "@tanstack/react-query": "^5.56.2", "@types/cors": "^2.8.17", + "@types/dompurify": "^3.0.5", "@types/express": "^4.17.21", "@types/node": "^22.5.5", "@types/nodemailer": "^7.0.3", @@ -6881,6 +6883,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/draco3d": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", @@ -7167,6 +7179,13 @@ "meshoptimizer": "~0.18.1" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -9853,6 +9872,15 @@ "dev": true, "license": "MIT" }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dotenv": { "version": "17.2.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", diff --git a/package.json b/package.json index 20f71d99..ba4e6936 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@vercel/analytics": "^1.5.0", "adm-zip": "^0.5.16", "chokidar": "^3.6.0", + "dompurify": "^3.3.1", "dotenv": "^17.2.0", "ethers": "^6.13.0", "express": "^4.18.2", @@ -83,6 +84,7 @@ "@tailwindcss/typography": "^0.5.15", "@tanstack/react-query": "^5.56.2", "@types/cors": "^2.8.17", + "@types/dompurify": "^3.0.5", "@types/express": "^4.17.21", "@types/node": "^22.5.5", "@types/nodemailer": "^7.0.3",