Skip to content

Commit 1d68a90

Browse files
MarcAmickMarc Amickdanny-avila
authored andcommitted
⚖️ fix: Add Configurable File Size Cap for Conversation Imports (danny-avila#10012)
* Check file size of conversation being imported against a configured max size to prevent bringing down the application by uploading a large file chore: remove non-english localization as needs to be added via locize * feat: Implement file size validation for conversation imports to prevent oversized uploads --------- Co-authored-by: Marc Amick <[email protected]> Co-authored-by: Danny Avila <[email protected]>
1 parent a667c40 commit 1d68a90

File tree

6 files changed

+38
-1
lines changed

6 files changed

+38
-1
lines changed

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,12 @@ HELP_AND_FAQ_URL=https://librechat.ai
650650
# Google tag manager id
651651
#ANALYTICS_GTM_ID=user provided google tag manager id
652652

653+
# limit conversation file imports to a certain number of bytes in size to avoid the container
654+
# maxing out memory limitations by unremarking this line and supplying a file size in bytes
655+
# such as the below example of 250 mib
656+
# CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES=262144000
657+
658+
653659
#===============#
654660
# REDIS Options #
655661
#===============#

api/server/routes/config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ router.get('/', async function (req, res) {
115115
sharePointPickerGraphScope: process.env.SHAREPOINT_PICKER_GRAPH_SCOPE,
116116
sharePointPickerSharePointScope: process.env.SHAREPOINT_PICKER_SHAREPOINT_SCOPE,
117117
openidReuseTokens,
118+
conversationImportMaxFileSize: process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES
119+
? parseInt(process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES, 10)
120+
: 0,
118121
};
119122

120123
const minPasswordLength = parseInt(process.env.MIN_PASSWORD_LENGTH, 10);

api/server/utils/import/importConversations.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,23 @@ const importConversations = async (job) => {
1010
const { filepath, requestUserId } = job;
1111
try {
1212
logger.debug(`user: ${requestUserId} | Importing conversation(s) from file...`);
13+
14+
/* error if file is too large */
15+
const fileInfo = await fs.stat(filepath);
16+
if (fileInfo.size > process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES) {
17+
throw new Error(
18+
`File size is ${fileInfo.size} bytes. It exceeds the maximum limit of ${process.env.CONVERSATION_IMPORT_MAX_FILE_SIZE_BYTES} bytes.`,
19+
);
20+
}
21+
1322
const fileData = await fs.readFile(filepath, 'utf8');
1423
const jsonData = JSON.parse(fileData);
1524
const importer = getImporter(jsonData);
1625
await importer(jsonData, requestUserId);
1726
logger.debug(`user: ${requestUserId} | Finished importing conversations`);
1827
} catch (error) {
1928
logger.error(`user: ${requestUserId} | Failed to import conversation: `, error);
29+
throw error; // throw error all the way up so request does not return success
2030
} finally {
2131
try {
2232
await fs.unlink(filepath);

client/src/components/Nav/SettingsTabs/Data/ImportConversations.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { useState, useRef, useCallback } from 'react';
22
import { Import } from 'lucide-react';
3+
import { useQueryClient } from '@tanstack/react-query';
4+
import { QueryKeys, TStartupConfig } from 'librechat-data-provider';
35
import { Spinner, useToastContext, Label, Button } from '@librechat/client';
46
import { useUploadConversationsMutation } from '~/data-provider';
57
import { NotificationSeverity } from '~/common';
68
import { useLocalize } from '~/hooks';
79
import { cn, logger } from '~/utils';
810

911
function ImportConversations() {
12+
const queryClient = useQueryClient();
13+
const startupConfig = queryClient.getQueryData<TStartupConfig>([QueryKeys.startupConfig]);
1014
const localize = useLocalize();
1115
const fileInputRef = useRef<HTMLInputElement>(null);
1216
const { showToast } = useToastContext();
@@ -49,6 +53,17 @@ function ImportConversations() {
4953
const handleFileUpload = useCallback(
5054
async (file: File) => {
5155
try {
56+
const maxFileSize = (startupConfig as any)?.conversationImportMaxFileSize;
57+
if (maxFileSize && file.size > maxFileSize) {
58+
const size = (maxFileSize / (1024 * 1024)).toFixed(2);
59+
showToast({
60+
message: localize('com_error_files_upload_too_large', { 0: size }),
61+
status: NotificationSeverity.ERROR,
62+
});
63+
setIsUploading(false);
64+
return;
65+
}
66+
5267
const formData = new FormData();
5368
formData.append('file', file, encodeURIComponent(file.name || 'File'));
5469
uploadFile.mutate(formData);
@@ -61,13 +76,14 @@ function ImportConversations() {
6176
});
6277
}
6378
},
64-
[uploadFile, showToast, localize],
79+
[uploadFile, showToast, localize, startupConfig],
6580
);
6681

6782
const handleFileChange = useCallback(
6883
(event: React.ChangeEvent<HTMLInputElement>) => {
6984
const file = event.target.files?.[0];
7085
if (file) {
86+
setIsUploading(true);
7187
handleFileUpload(file);
7288
}
7389
event.target.value = '';

client/src/locales/en/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@
365365
"com_error_files_process": "An error occurred while processing the file.",
366366
"com_error_files_upload": "An error occurred while uploading the file.",
367367
"com_error_files_upload_canceled": "The file upload request was canceled. Note: the file upload may still be processing and will need to be manually deleted.",
368+
"com_error_files_upload_too_large": "The file is too large. Please upload a file smaller than {{0}} MB",
368369
"com_error_files_validation": "An error occurred while validating the file.",
369370
"com_error_google_tool_conflict": "Usage of built-in Google tools are not supported with external tools. Please disable either the built-in tools or the external tools.",
370371
"com_error_heic_conversion": "Failed to convert HEIC image to JPEG. Please try converting the image manually or use a different format.",

packages/data-provider/src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ export type TStartupConfig = {
669669
}
670670
>;
671671
mcpPlaceholder?: string;
672+
conversationImportMaxFileSize?: number;
672673
};
673674

674675
export enum OCRStrategy {

0 commit comments

Comments
 (0)