Skip to content

Commit 3e9abae

Browse files
committed
Sanitize notification action url to block unsafe protocols
1 parent 07a8d4d commit 3e9abae

1 file changed

Lines changed: 10 additions & 7 deletions

File tree

apps/webapp/app/components/navigation/NotificationCard.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ export function NotificationCard({
6666
setIsExpanded((v) => !v);
6767
};
6868

69+
const safeActionUrl = sanitizeUrl(actionUrl);
70+
const safeImage = sanitizeUrl(image);
71+
6972
return (
7073
<div className="group/card relative overflow-hidden rounded border border-charcoal-650 bg-charcoal-700/50 shadow-lg">
7174
{safeActionUrl && (
@@ -106,7 +109,7 @@ export function NotificationCard({
106109
</button>
107110
)}
108111

109-
{image && <img src={sanitizeImageUrl(image)} alt="" className="mt-1.5 rounded" />}
112+
{safeImage && <img src={safeImage} alt="" className="mt-1.5 rounded" />}
110113
</div>
111114
</div>
112115
);
@@ -141,14 +144,14 @@ function getMarkdownComponents(onLinkClick?: () => void) {
141144
};
142145
}
143146

144-
/** Sanitize image URL to prevent XSS via javascript: or data: URIs. */
145-
function sanitizeImageUrl(url: string): string {
147+
const SAFE_URL_PROTOCOLS = new Set(["http:", "https:", "mailto:", "tel:"]);
148+
149+
/** Sanitize a URL to prevent XSS via javascript: or data: URIs. Returns "" if invalid. */
150+
function sanitizeUrl(url: string | undefined): string {
151+
if (!url) return "";
146152
try {
147153
const parsed = new URL(url);
148-
if (parsed.protocol === "https:" || parsed.protocol === "http:") {
149-
return parsed.href;
150-
}
151-
return "";
154+
return SAFE_URL_PROTOCOLS.has(parsed.protocol) ? parsed.href : "";
152155
} catch {
153156
return "";
154157
}

0 commit comments

Comments
 (0)