diff --git a/api/server/routes/files/files.agents.test.js b/api/server/routes/files/files.agents.test.js
index bdbdb8837f..7c21e95234 100644
--- a/api/server/routes/files/files.agents.test.js
+++ b/api/server/routes/files/files.agents.test.js
@@ -608,5 +608,114 @@ describe('File Routes - Agent Files Endpoint', () => {
expect(response.status).toBe(200);
expect(processAgentFileUpload).toHaveBeenCalled();
});
+
+ it('should allow message_file attachment to agent even without EDIT permission', async () => {
+ // Create an agent owned by authorId
+ const agent = await createAgent({
+ id: agentCustomId,
+ name: 'Test Agent',
+ provider: 'openai',
+ model: 'gpt-4',
+ author: authorId,
+ });
+
+ // Grant only VIEW permission to otherUserId
+ const { grantPermission } = require('~/server/services/PermissionService');
+ await grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: otherUserId,
+ resourceType: ResourceType.AGENT,
+ resourceId: agent._id,
+ accessRoleId: AccessRoleIds.AGENT_VIEWER,
+ grantedBy: authorId,
+ });
+
+ const testApp = createAppWithUser(otherUserId);
+
+ // message_file: true indicates this is a chat message attachment, not a permanent file upload
+ const response = await request(testApp).post('/files').send({
+ endpoint: 'agents',
+ agent_id: agentCustomId,
+ tool_resource: 'context',
+ message_file: true,
+ file_id: uuidv4(),
+ });
+
+ expect(response.status).toBe(200);
+ expect(processAgentFileUpload).toHaveBeenCalled();
+ });
+
+ it('should allow message_file attachment (string "true") to agent even without EDIT permission', async () => {
+ // Create an agent owned by authorId
+ const agent = await createAgent({
+ id: agentCustomId,
+ name: 'Test Agent',
+ provider: 'openai',
+ model: 'gpt-4',
+ author: authorId,
+ });
+
+ // Grant only VIEW permission to otherUserId
+ const { grantPermission } = require('~/server/services/PermissionService');
+ await grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: otherUserId,
+ resourceType: ResourceType.AGENT,
+ resourceId: agent._id,
+ accessRoleId: AccessRoleIds.AGENT_VIEWER,
+ grantedBy: authorId,
+ });
+
+ const testApp = createAppWithUser(otherUserId);
+
+ // message_file as string "true" (from form data) should also be allowed
+ const response = await request(testApp).post('/files').send({
+ endpoint: 'agents',
+ agent_id: agentCustomId,
+ tool_resource: 'context',
+ message_file: 'true',
+ file_id: uuidv4(),
+ });
+
+ expect(response.status).toBe(200);
+ expect(processAgentFileUpload).toHaveBeenCalled();
+ });
+
+ it('should deny file upload when message_file is false (not a message attachment)', async () => {
+ // Create an agent owned by authorId
+ const agent = await createAgent({
+ id: agentCustomId,
+ name: 'Test Agent',
+ provider: 'openai',
+ model: 'gpt-4',
+ author: authorId,
+ });
+
+ // Grant only VIEW permission to otherUserId
+ const { grantPermission } = require('~/server/services/PermissionService');
+ await grantPermission({
+ principalType: PrincipalType.USER,
+ principalId: otherUserId,
+ resourceType: ResourceType.AGENT,
+ resourceId: agent._id,
+ accessRoleId: AccessRoleIds.AGENT_VIEWER,
+ grantedBy: authorId,
+ });
+
+ const testApp = createAppWithUser(otherUserId);
+
+ // message_file: false should NOT bypass permission check
+ const response = await request(testApp).post('/files').send({
+ endpoint: 'agents',
+ agent_id: agentCustomId,
+ tool_resource: 'context',
+ message_file: false,
+ file_id: uuidv4(),
+ });
+
+ expect(response.status).toBe(403);
+ expect(response.body.error).toBe('Forbidden');
+ expect(processAgentFileUpload).not.toHaveBeenCalled();
+ });
});
});
diff --git a/api/server/routes/files/files.js b/api/server/routes/files/files.js
index 6d18096023..5de2ddb379 100644
--- a/api/server/routes/files/files.js
+++ b/api/server/routes/files/files.js
@@ -381,8 +381,14 @@ router.post('/', async (req, res) => {
return await processFileUpload({ req, res, metadata });
}
- /** Check agent permissions for agent file uploads (not message attachments) */
- if (metadata.agent_id && metadata.tool_resource) {
+ /**
+ * Check agent permissions for permanent agent file uploads (not message attachments).
+ * Message attachments (message_file=true) are temporary files for a single conversation
+ * and should be allowed for users who can chat with the agent.
+ * Permanent file uploads to tool_resources require EDIT permission.
+ */
+ const isMessageAttachment = metadata.message_file === true || metadata.message_file === 'true';
+ if (metadata.agent_id && metadata.tool_resource && !isMessageAttachment) {
const userId = req.user.id;
/** Admin users bypass permission checks */
diff --git a/client/src/components/Chat/Input/ChatForm.tsx b/client/src/components/Chat/Input/ChatForm.tsx
index b7cd2e6e9a..cb1e30a09d 100644
--- a/client/src/components/Chat/Input/ChatForm.tsx
+++ b/client/src/components/Chat/Input/ChatForm.tsx
@@ -320,7 +320,9 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {