diff --git a/bun.lock b/bun.lock index a577d1fb..139068ec 100644 --- a/bun.lock +++ b/bun.lock @@ -30,6 +30,7 @@ "@xterm/xterm": "^5.5.0", "clsx": "^2.1.1", "dayjs": "^1.11.19", + "dompurify": "^3.3.1", "fast-deep-equal": "^3.1.3", "ignore": "^7.0.5", "immer": "^10.2.0", @@ -54,6 +55,7 @@ "@tailwindcss/vite": "^4.1.17", "@tauri-apps/cli": "^2.9.5", "@types/bun": "^1.3.3", + "@types/dompurify": "^3.2.0", "@types/node": "^24.10.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", @@ -473,6 +475,8 @@ "@types/conventional-commits-parser": ["@types/conventional-commits-parser@5.0.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ=="], + "@types/dompurify": ["@types/dompurify@3.2.0", "", { "dependencies": { "dompurify": "*" } }, "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], @@ -485,6 +489,8 @@ "@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="], + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], "@vue/compiler-core": ["@vue/compiler-core@3.5.18", "", { "dependencies": { "@babel/parser": "^7.28.0", "@vue/shared": "3.5.18", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw=="], @@ -589,6 +595,8 @@ "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + "dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="], + "dot-prop": ["dot-prop@5.3.0", "", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], diff --git a/package.json b/package.json index 1fdbd6b6..bccc54e9 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@xterm/xterm": "^5.5.0", "clsx": "^2.1.1", "dayjs": "^1.11.19", + "dompurify": "^3.3.1", "fast-deep-equal": "^3.1.3", "ignore": "^7.0.5", "immer": "^10.2.0", @@ -76,6 +77,7 @@ "@tailwindcss/vite": "^4.1.17", "@tauri-apps/cli": "^2.9.5", "@types/bun": "^1.3.3", + "@types/dompurify": "^3.2.0", "@types/node": "^24.10.1", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", diff --git a/src/features/editor/markdown/parser.ts b/src/features/editor/markdown/parser.ts index ab11298b..ceab3e37 100644 --- a/src/features/editor/markdown/parser.ts +++ b/src/features/editor/markdown/parser.ts @@ -1,3 +1,4 @@ +import DOMPurify from "dompurify"; import { normalizeLanguage } from "./language-map"; import { highlightCode } from "./prism-languages"; @@ -198,5 +199,6 @@ export function parseMarkdown(content: string): string { processedLines.push(""); } - return processedLines.join("\n"); + const rawHtml = processedLines.join("\n"); + return DOMPurify.sanitize(rawHtml); }