Add path validation for chat directory operations

83a16d144c9a34171e7a2908fd585e540d126fce

Cohee <18619528+Cohee1207@users.noreply.github.com>

1 files changed, +24 -3Ignore whitespace
src/endpoints/chats.js+24 -3
@@ -20,6 +20,7 @@ import {
20 tryReadFileSync,20 tryReadFileSync,
21 tryDeleteFile,21 tryDeleteFile,
22 readFirstLine,22 readFirstLine,
23 isPathUnderParent,
23} from '../util.js';24} from '../util.js';
2425
25const isBackupEnabled = !!getConfigValue('backups.chat.enabled', true, 'boolean');26const isBackupEnabled = !!getConfigValue('backups.chat.enabled', true, 'boolean');
@@ -473,6 +474,9 @@ router.post('/save', validateAvatarUrlMiddleware, async function (request, respo
473 const chatData = request.body.chat;474 const chatData = request.body.chat;
474 const chatFileName = `${String(request.body.file_name)}.jsonl`;475 const chatFileName = `${String(request.body.file_name)}.jsonl`;
475 const chatFilePath = path.join(request.user.directories.chats, cardName, sanitize(chatFileName));476 const chatFilePath = path.join(request.user.directories.chats, cardName, sanitize(chatFileName));
477 if (!isPathUnderParent(request.user.directories.chats, chatFilePath)) {
478 return response.sendStatus(400);
479 }
476480
477 if (Array.isArray(chatData)) {481 if (Array.isArray(chatData)) {
478 await trySaveChat(chatData, chatFilePath, request.body.force, handle, cardName, request.user.directories.backups);482 await trySaveChat(chatData, chatFilePath, request.body.force, handle, cardName, request.user.directories.backups);
@@ -514,6 +518,9 @@ router.post('/get', validateAvatarUrlMiddleware, function (request, response) {
514 try {518 try {
515 const dirName = String(request.body.avatar_url).replace('.png', '');519 const dirName = String(request.body.avatar_url).replace('.png', '');
516 const directoryPath = path.join(request.user.directories.chats, dirName);520 const directoryPath = path.join(request.user.directories.chats, dirName);
521 if (!isPathUnderParent(request.user.directories.chats, directoryPath)) {
522 return response.sendStatus(400);
523 }
517 const chatDirExists = fs.existsSync(directoryPath);524 const chatDirExists = fs.existsSync(directoryPath);
518525
519 //if no chat dir for the character is found, make one with the character name526 //if no chat dir for the character is found, make one with the character name
@@ -545,6 +552,9 @@ router.post('/rename', validateAvatarUrlMiddleware, async function (request, res
545 const pathToFolder = request.body.is_group552 const pathToFolder = request.body.is_group
546 ? request.user.directories.groupChats553 ? request.user.directories.groupChats
547 : path.join(request.user.directories.chats, String(request.body.avatar_url).replace('.png', ''));554 : path.join(request.user.directories.chats, String(request.body.avatar_url).replace('.png', ''));
555 if (!request.body.is_group && !isPathUnderParent(request.user.directories.chats, pathToFolder)) {
556 return response.sendStatus(400);
557 }
548 const pathToOriginalFile = path.join(pathToFolder, sanitize(request.body.original_file));558 const pathToOriginalFile = path.join(pathToFolder, sanitize(request.body.original_file));
549 const pathToRenamedFile = path.join(pathToFolder, sanitize(request.body.renamed_file));559 const pathToRenamedFile = path.join(pathToFolder, sanitize(request.body.renamed_file));
550 const sanitizedFileName = path.parse(pathToRenamedFile).name;560 const sanitizedFileName = path.parse(pathToRenamedFile).name;
@@ -575,6 +585,9 @@ router.post('/delete', validateAvatarUrlMiddleware, function (request, response)
575 const dirName = String(request.body.avatar_url).replace('.png', '');585 const dirName = String(request.body.avatar_url).replace('.png', '');
576 const chatFileName = String(request.body.chatfile);586 const chatFileName = String(request.body.chatfile);
577 const chatFilePath = path.join(request.user.directories.chats, dirName, sanitize(chatFileName));587 const chatFilePath = path.join(request.user.directories.chats, dirName, sanitize(chatFileName));
588 if (!isPathUnderParent(request.user.directories.chats, chatFilePath)) {
589 return response.sendStatus(400);
590 }
578 //Return success if the file was deleted.591 //Return success if the file was deleted.
579 if (tryDeleteFile(chatFilePath)) {592 if (tryDeleteFile(chatFilePath)) {
580 return response.send({ ok: true });593 return response.send({ ok: true });
@@ -595,7 +608,10 @@ router.post('/export', validateAvatarUrlMiddleware, async function (request, res
595 const pathToFolder = request.body.is_group608 const pathToFolder = request.body.is_group
596 ? request.user.directories.groupChats609 ? request.user.directories.groupChats
597 : path.join(request.user.directories.chats, String(request.body.avatar_url).replace('.png', ''));610 : path.join(request.user.directories.chats, String(request.body.avatar_url).replace('.png', ''));
598 let filename = path.join(pathToFolder, sanitize(request.body.file));611 const filename = path.join(pathToFolder, sanitize(request.body.file));
612 if (!request.body.is_group && !isPathUnderParent(request.user.directories.chats, filename)) {
613 return response.sendStatus(400);
614 }
599 let exportfilename = request.body.exportfilename;615 let exportfilename = request.body.exportfilename;
600 if (!fs.existsSync(filename)) {616 if (!fs.existsSync(filename)) {
601 const errorMessage = {617 const errorMessage = {
@@ -690,6 +706,11 @@ router.post('/import', validateAvatarUrlMiddleware, function (request, response)
690 return response.sendStatus(400);706 return response.sendStatus(400);
691 }707 }
692708
709 const directoryPath = path.join(request.user.directories.chats, avatarUrl);
710 if (!isPathUnderParent(request.user.directories.chats, directoryPath)) {
711 return response.sendStatus(400);
712 }
713
693 try {714 try {
694 const pathToUpload = path.join(request.file.destination, request.file.filename);715 const pathToUpload = path.join(request.file.destination, request.file.filename);
695 const data = fs.readFileSync(pathToUpload, 'utf8');716 const data = fs.readFileSync(pathToUpload, 'utf8');
@@ -718,7 +739,7 @@ router.post('/import', validateAvatarUrlMiddleware, function (request, response)
718739
719 const handleChat = (chat) => {740 const handleChat = (chat) => {
720 const fileName = `${characterName} - ${humanizedDateTime()} imported.jsonl`;741 const fileName = `${characterName} - ${humanizedDateTime()} imported.jsonl`;
721 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);742 const filePath = path.join(directoryPath, fileName);
722 fileNames.push(fileName);743 fileNames.push(fileName);
723 writeFileAtomicSync(filePath, chat, 'utf8');744 writeFileAtomicSync(filePath, chat, 'utf8');
724 };745 };
@@ -757,7 +778,7 @@ router.post('/import', validateAvatarUrlMiddleware, function (request, response)
757 }778 }
758779
759 const fileName = `${characterName} - ${humanizedDateTime()} imported.jsonl`;780 const fileName = `${characterName} - ${humanizedDateTime()} imported.jsonl`;
760 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);781 const filePath = path.join(directoryPath, fileName);
761 fileNames.push(fileName);782 fileNames.push(fileName);
762 if (flattenedChat !== data) {783 if (flattenedChat !== data) {
763 writeFileAtomicSync(filePath, flattenedChat, 'utf8');784 writeFileAtomicSync(filePath, flattenedChat, 'utf8');