mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
Fix potential IDOR vulnerability in workspace parsed files endpoints
Add ownership validation to prevent users from deleting or embedding parsed files that don't belong to them. Previously, the delete and embed endpoints only validated authentication but not resource ownership, allowing users to delete attached files for users within workspaces they are also a member of. Changes: - Delete endpoint now filters by userId and workspaceId - Embed endpoint validates file belongs to user and workspace (redundant) - delete() returns false when no matching records found (returns 403) - Added JSDoc comments for clarity GHSA-p5rf-8p88-979c
This commit is contained in:
@@ -50,10 +50,16 @@ function workspaceParsedFilesEndpoints(app) {
|
||||
try {
|
||||
const { fileIds = [] } = reqBody(request);
|
||||
if (!fileIds.length) return response.sendStatus(400).end();
|
||||
const user = await userFromSession(request, response);
|
||||
const workspace = response.locals.workspace;
|
||||
const success = await WorkspaceParsedFiles.delete({
|
||||
id: { in: fileIds.map((id) => parseInt(id)) },
|
||||
id: {
|
||||
in: fileIds.map((id) => parseInt(id)),
|
||||
},
|
||||
...(user ? { userId: user.id } : {}),
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
return response.status(success ? 200 : 500).end();
|
||||
return response.status(success ? 200 : 403).end();
|
||||
} catch (e) {
|
||||
console.error(e.message, e);
|
||||
return response.sendStatus(500).end();
|
||||
@@ -77,7 +83,11 @@ function workspaceParsedFilesEndpoints(app) {
|
||||
|
||||
if (!fileId) return response.sendStatus(400).end();
|
||||
const { success, error, document } =
|
||||
await WorkspaceParsedFiles.moveToDocumentsAndEmbed(fileId, workspace);
|
||||
await WorkspaceParsedFiles.moveToDocumentsAndEmbed(
|
||||
user,
|
||||
fileId,
|
||||
workspace
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
return response.status(500).json({
|
||||
|
||||
@@ -43,6 +43,11 @@ const WorkspaceParsedFiles = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a parsed file by its ID or a clause.
|
||||
* @param {object} clause - The clause to filter the parsed files.
|
||||
* @returns {Promise<import("@prisma/client").workspace_parsed_files | null>} The parsed file.
|
||||
*/
|
||||
get: async function (clause = {}) {
|
||||
try {
|
||||
const file = await prisma.workspace_parsed_files.findFirst({
|
||||
@@ -77,10 +82,10 @@ const WorkspaceParsedFiles = {
|
||||
|
||||
delete: async function (clause = {}) {
|
||||
try {
|
||||
await prisma.workspace_parsed_files.deleteMany({
|
||||
const result = await prisma.workspace_parsed_files.deleteMany({
|
||||
where: clause,
|
||||
});
|
||||
return true;
|
||||
return result.count > 0;
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
return false;
|
||||
@@ -95,9 +100,20 @@ const WorkspaceParsedFiles = {
|
||||
return _sum.tokenCountEstimate || 0;
|
||||
},
|
||||
|
||||
moveToDocumentsAndEmbed: async function (fileId, workspace) {
|
||||
/**
|
||||
* Moves a parsed file to the documents and embeds it.
|
||||
* @param {import("@prisma/client").users | null} user - The user performing the operation.
|
||||
* @param {number} fileId - The ID of the parsed file.
|
||||
* @param {import("@prisma/client").workspaces} workspace - The workspace the file belongs to.
|
||||
* @returns {Promise<{ success: boolean, error: string | null, document: import("@prisma/client").workspace_documents | null }>} The result of the operation.
|
||||
*/
|
||||
moveToDocumentsAndEmbed: async function (user = null, fileId, workspace) {
|
||||
try {
|
||||
const parsedFile = await this.get({ id: parseInt(fileId) });
|
||||
const parsedFile = await this.get({
|
||||
id: parseInt(fileId),
|
||||
...(user ? { userId: user.id } : {}),
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
if (!parsedFile) throw new Error("File not found");
|
||||
|
||||
// Get file location from metadata
|
||||
|
||||
Reference in New Issue
Block a user