diff --git a/CHANGELOG.md b/CHANGELOG.md
index 367271613..2d1aff7f4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,10 @@ and this project adheres to
## [Unreleased]
+## Fixed
+
+- 🔒(frontend) enhance file download security #889
+
## Added
- 🚸(backend) make document search on title accent-insensitive #874
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/FileDownloadButton.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/FileDownloadButton.tsx
index 75dacd3d4..e0c318478 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/FileDownloadButton.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteToolBar/FileDownloadButton.tsx
@@ -15,6 +15,7 @@ import { useCallback, useMemo } from 'react';
import { RiDownload2Fill } from 'react-icons/ri';
import { downloadFile, exportResolveFileUrl } from '@/docs/doc-export';
+import { isSafeUrl } from '@/utils/url';
export const FileDownloadButton = ({
open,
@@ -59,7 +60,11 @@ export const FileDownloadButton = ({
*/
if (!url.includes(window.location.hostname) && !url.includes('base64')) {
if (!editor.resolveFileUrl) {
- window.open(url);
+ if (!isSafeUrl(url)) {
+ return;
+ }
+
+ window.open(url, '_blank', 'noopener,noreferrer');
} else {
void editor
.resolveFileUrl(url)
diff --git a/src/frontend/apps/impress/src/utils/__tests__/url.test.tsx b/src/frontend/apps/impress/src/utils/__tests__/url.test.tsx
new file mode 100644
index 000000000..26e33fc7b
--- /dev/null
+++ b/src/frontend/apps/impress/src/utils/__tests__/url.test.tsx
@@ -0,0 +1,110 @@
+import { isSafeUrl } from '@/utils/url';
+
+describe('isSafeUrl', () => {
+ // XSS Attacks
+ const xssUrls = [
+ "javascript:alert('xss')",
+ "data:text/html,",
+ "vbscript:msgbox('xss')",
+ "expression(alert('xss'))",
+ "https://example.com/\">",
+ "https://example.com/\">
",
+ "javascript:/*-->