Skip to content

Commit dce1d03

Browse files
authored
fix: xss in link toolbar and file download (#1060)
* fix: xss in link toolbar and file download * fix: linter warning
1 parent d6f553b commit dce1d03

File tree

3 files changed

+27
-2
lines changed

3 files changed

+27
-2
lines changed

packages/react/src/components/FormattingToolbar/DefaultButtons/FileDownloadButton.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useComponentsContext } from "../../../editor/ComponentsContext";
1212
import { useBlockNoteEditor } from "../../../hooks/useBlockNoteEditor";
1313
import { useSelectedBlocks } from "../../../hooks/useSelectedBlocks";
1414
import { useDictionary } from "../../../i18n/dictionary";
15+
import { sanitizeUrl } from "../../../util/sanitizeUrl";
1516

1617
export const FileDownloadButton = () => {
1718
const dict = useDictionary();
@@ -45,7 +46,9 @@ export const FileDownloadButton = () => {
4546
editor.focus();
4647
editor
4748
.resolveFileUrl(fileBlock.props.url)
48-
.then((downloadUrl) => window.open(downloadUrl));
49+
.then((downloadUrl) =>
50+
window.open(sanitizeUrl(downloadUrl, window.location.href))
51+
);
4952
}
5053
}, [editor, fileBlock]);
5154

packages/react/src/components/LinkToolbar/DefaultButtons/OpenLinkButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { RiExternalLinkFill } from "react-icons/ri";
22
import { useComponentsContext } from "../../../editor/ComponentsContext";
33
import { useDictionary } from "../../../i18n/dictionary";
44
import { LinkToolbarProps } from "../LinkToolbarProps";
5+
import { sanitizeUrl } from "../../../util/sanitizeUrl";
56

67
export const OpenLinkButton = (props: Pick<LinkToolbarProps, "url">) => {
78
const Components = useComponentsContext()!;
@@ -14,7 +15,7 @@ export const OpenLinkButton = (props: Pick<LinkToolbarProps, "url">) => {
1415
label={dict.link_toolbar.open.tooltip}
1516
isSelected={false}
1617
onClick={() => {
17-
window.open(props.url, "_blank");
18+
window.open(sanitizeUrl(props.url, window.location.href), "_blank");
1819
}}
1920
icon={<RiExternalLinkFill />}
2021
/>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Sanitizes a potentially unsafe URL.
3+
* @param {string} inputUrl - The URL to sanitize.
4+
* @param {string} baseUrl - The base URL to use for relative URLs.
5+
* @returns {string} The normalized URL, or "#" if the URL is invalid or unsafe.
6+
*/
7+
export function sanitizeUrl(inputUrl: string, baseUrl: string): string {
8+
try {
9+
const url = new URL(inputUrl, baseUrl);
10+
11+
// eslint-disable-next-line no-script-url -- false positive, we are explicitly checking if the protocol is safe to prevent XSS
12+
if (url.protocol !== "javascript:") {
13+
return url.href;
14+
}
15+
} catch (error) {
16+
// if URL creation fails, it's an invalid URL
17+
}
18+
19+
// return a safe default for invalid or unsafe URLs
20+
return "#";
21+
}

0 commit comments

Comments
 (0)