Skip to content

Commit 3aa2d93

Browse files
security: sanitize all dangerouslySetInnerHTML usage with DOMPurify (#5588)
Apply DOMPurify.sanitize() to all remaining dangerouslySetInnerHTML instances that were not already sanitized: - ChangelogModal: RSS feed content rendering - textbookCourse: marked.parse() markdown-to-HTML output - adminSettings: search highlight markup - EmptyStateCard: Shiki code highlighting output Prevents stored XSS via injected HTML/JS in rendered content. DOMPurify was already a dependency and used in CodeHighlighter and ErrorMessage components.
1 parent af9743e commit 3aa2d93

File tree

4 files changed

+8
-4
lines changed

4 files changed

+8
-4
lines changed

web/components/layout/ChangelogModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
DialogTitle,
66
} from "@/components/ui/dialog";
77
import { ChangelogItem } from "./auth/types";
8+
import DOMPurify from "dompurify";
89

910
const ChangelogModal = ({
1011
open,
@@ -36,7 +37,7 @@ const ChangelogModal = ({
3637
)}
3738
<div
3839
className="prose prose-sm dark:prose-invert prose-h2:text-base prose-h3:text-base"
39-
dangerouslySetInnerHTML={{ __html: changelog["content:encoded"] }}
40+
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(changelog["content:encoded"]) }}
4041
/>
4142
</div>
4243
</DialogContent>

web/components/shared/helicone/EmptyStateCard.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Button } from "@/components/ui/button";
22
import { H2, P } from "@/components/ui/typography";
3+
import DOMPurify from "dompurify";
34
import {
45
Archive,
56
Bell,
@@ -311,7 +312,7 @@ const ShikiHighlightedCode: React.FC<{
311312
<div className="w-full overflow-hidden rounded-lg">
312313
<div
313314
className={`overflow-x-auto rounded-lg bg-[#24292e] p-4 text-left max-w-${maxWidth} mx-auto`}
314-
dangerouslySetInnerHTML={{ __html: highlightedCode }}
315+
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(highlightedCode) }}
315316
/>
316317
</div>
317318
);

web/components/shared/themed/demo/textbookCourse.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import "prismjs/components/prism-clike";
88
import "prismjs/components/prism-javascript";
99
import "prismjs/components/prism-json";
1010
import { Course } from "./types";
11+
import DOMPurify from "dompurify";
1112
import Link from "next/link";
1213
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/solid";
1314
import { SESSION_NAME } from "./courseGenerator";
@@ -74,7 +75,7 @@ const TextbookCourse: React.FC<TextbookCourseProps> = ({
7475
const html = marked.parse(content) as string;
7576
return (
7677
<div
77-
dangerouslySetInnerHTML={{ __html: html }}
78+
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html) }}
7879
className="prose prose-indigo max-w-none"
7980
style={{
8081
fontFamily: '"Fira Code", "Fira Mono", monospace',

web/components/templates/admin/adminSettings.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useMemo } from "react";
2+
import DOMPurify from "dompurify";
23
import { $JAWN_API } from "../../../lib/clients/jawn";
34
import { Button } from "@/components/ui/button";
45
import { H1, P, Lead, Small } from "@/components/ui/typography";
@@ -400,7 +401,7 @@ const AdminSettings = () => {
400401
<div className="flex items-center">
401402
<p
402403
className="font-sans m-0 text-base font-medium leading-7 text-[hsl(var(--foreground))]"
403-
dangerouslySetInnerHTML={{ __html: displayName }}
404+
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(displayName) }}
404405
/>
405406
{isSensitive && (
406407
<span className="ml-2 rounded-full bg-amber-100 px-2 py-0.5 text-xs text-amber-800 dark:bg-amber-900 dark:text-amber-100">

0 commit comments

Comments
 (0)