From bed7f35777eb6a9e52a266fe72f1612b156addb3 Mon Sep 17 00:00:00 2001 From: aqilaziz Date: Sun, 10 May 2026 21:27:38 +0700 Subject: [PATCH] fix: sanitize markdown renderer --- bun.lock | 5 ++ package.json | 1 + src/components/ui/MarkdownRenderer.tsx | 95 +++++++++++++++----------- 3 files changed, 60 insertions(+), 41 deletions(-) diff --git a/bun.lock b/bun.lock index 84b8a3f..91c1bcc 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,7 @@ "react-dom": "^19.2.4", "react-markdown": "^10.1.0", "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", "vite": "^7.3.1", @@ -472,6 +473,8 @@ "hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], + "hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="], + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], "hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="], @@ -690,6 +693,8 @@ "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], + "rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="], + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], diff --git a/package.json b/package.json index c784fd2..89d9182 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "react-dom": "^19.2.4", "react-markdown": "^10.1.0", "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", "vite": "^7.3.1", diff --git a/src/components/ui/MarkdownRenderer.tsx b/src/components/ui/MarkdownRenderer.tsx index 8abe872..8ad32a2 100644 --- a/src/components/ui/MarkdownRenderer.tsx +++ b/src/components/ui/MarkdownRenderer.tsx @@ -1,6 +1,7 @@ import type { CSSProperties } from 'react'; import ReactMarkdown, { type Components } from 'react-markdown'; import rehypeRaw from 'rehype-raw'; +import rehypeSanitize from 'rehype-sanitize'; interface MarkdownRendererProps { content: string; @@ -8,54 +9,66 @@ interface MarkdownRendererProps { style?: CSSProperties; } -function withoutNode(props: T) { - const rest = { ...props }; - delete rest.node; - return rest; -} +const rehypePlugins = [rehypeRaw, rehypeSanitize]; const markdownComponents: Components = { - img: ({ ...props }) => ( - - ), - a: ({ ...props }) => ( - - ), - p: ({ ...props }) => ( -

- ), - pre: ({ ...props }) => ( -

-  ),
-  code: ({ ...props }) => (
-    
-  ),
+  img: ({ node, ...props }) => {
+    void node;
+
+    return (
+      
+    );
+  },
+  a: ({ node, ...props }) => {
+    void node;
+
+    return (
+      
+    );
+  },
+  p: ({ node, ...props }) => {
+    void node;
+
+    return 

; + }, + pre: ({ node, ...props }) => { + void node; + + return ( +

+    );
+  },
+  code: ({ node, ...props }) => {
+    void node;
+
+    return ;
+  },
 };
 
 export function MarkdownRenderer({ content, className, style }: MarkdownRendererProps) {
   return (
     
- + {content}