🔒️(frontend) harden security check on url

We harden the security check on url to prevent attacks.
This commit is contained in:
Anthony LC
2026-02-05 17:24:04 +01:00
parent b8c1504e7a
commit d02c6250c9
7 changed files with 23 additions and 11 deletions

View File

@@ -1,5 +1,8 @@
import { APIError, errorCauses } from '@/api';
import { sleep } from '@/utils';
import { isSafeUrl } from '@/utils/url';
import { ANALYZE_URL } from '../conf';
interface CheckDocMediaStatusResponse {
file?: string;
@@ -13,6 +16,10 @@ interface CheckDocMediaStatus {
export const checkDocMediaStatus = async ({
urlMedia,
}: CheckDocMediaStatus): Promise<CheckDocMediaStatusResponse> => {
if (!isSafeUrl(urlMedia) || !urlMedia.includes(ANALYZE_URL)) {
throw new APIError('Url invalid', { status: 400 });
}
const response = await fetch(urlMedia, {
credentials: 'include',
});

View File

@@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components';
import { Box, Icon, Loading } from '@/components';
import { isSafeUrl } from '@/utils/url';
import Warning from '../../assets/warning.svg';
import { ANALYZE_URL } from '../../conf';
@@ -87,7 +88,7 @@ const PdfBlockComponent = ({ editor, block }: PdfBlockComponentProps) => {
}, [lang, t]);
useEffect(() => {
if (!pdfUrl || pdfUrl.includes(ANALYZE_URL)) {
if (!pdfUrl || pdfUrl.includes(ANALYZE_URL) || !isSafeUrl(pdfUrl)) {
return;
}
@@ -115,7 +116,8 @@ const PdfBlockComponent = ({ editor, block }: PdfBlockComponentProps) => {
}, [pdfUrl]);
const isInvalidPDF =
!isPDFContentLoading && isPDFContent !== null && !isPDFContent;
(!isPDFContentLoading && isPDFContent !== null && !isPDFContent) ||
!isSafeUrl(pdfUrl);
if (isInvalidPDF) {
return (

View File

@@ -11,6 +11,7 @@ import { useEffect } from 'react';
import { Box, Text } from '@/components';
import { useMediaUrl } from '@/core';
import { isSafeUrl } from '@/utils/url';
import { loopCheckDocMediaStatus } from '../../api';
import Loader from '../../assets/loader.svg';
@@ -67,7 +68,7 @@ const UploadLoaderBlockComponent = ({
block.props.type === 'loading' &&
isEditable;
if (!shouldCheckStatus) {
if (!shouldCheckStatus || !isSafeUrl(block.props.blockUploadUrl)) {
return;
}

View File

@@ -4,6 +4,7 @@ import { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { backendUrl } from '@/api';
import { isSafeUrl } from '@/utils/url';
import { useCreateDocAttachment } from '../api';
import { ANALYZE_URL } from '../conf';
@@ -57,7 +58,8 @@ export const useUploadStatus = (editor: DocsBlockNoteEditor) => {
if (
!block ||
!('url' in block.props) ||
('url' in block.props && !block.props.url.includes(ANALYZE_URL))
('url' in block.props && !block.props.url.includes(ANALYZE_URL)) ||
!isSafeUrl(block.props.url)
) {
return;
}

View File

@@ -1,5 +1,7 @@
import JSZip from 'jszip';
import { isSafeUrl } from '@/utils/url';
import { exportResolveFileUrl } from './api';
// Escape user-provided text before injecting it into the exported HTML document.
@@ -414,7 +416,7 @@ export const addMediaFilesToZip = async (
url = null;
}
if (!url || url.origin !== mediaUrl) {
if (!url || url.origin !== mediaUrl || !isSafeUrl(url.href)) {
return;
}

View File

@@ -13,9 +13,7 @@ const getDocVersion = async ({
versionId,
docId,
}: DocVersionParam): Promise<Version> => {
const url = `documents/${docId}/versions/${versionId}/`;
const response = await fetchAPI(url);
const response = await fetchAPI(`documents/${docId}/versions/${versionId}/`);
if (!response.ok) {
throw new APIError(

View File

@@ -29,9 +29,9 @@ const getDocVersions = async ({
versionId,
docId,
}: DocVersionsAPIParams): Promise<VersionsResponse> => {
const url = `documents/${docId}/versions/?version_id=${versionId}`;
const response = await fetchAPI(url);
const response = await fetchAPI(
`documents/${docId}/versions/?version_id=${versionId}`,
);
if (!response.ok) {
throw new APIError(