mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(workspace): harden chat-upload base-path containment + cleanup guard
- resolveAgentBasePath: the relative-override branch now normalizes the resolved path and rejects values that escape the workspace root via "../" (the absolute branch already did this), keeping attachment/media/tool I/O contained when an agent's workspaceBasePath is a relative override. - cleanAttachmentFiles: return early on a null/blank conversationId so a bare upload root can never be walked and deleted wholesale. - Translate the chat-upload Javadoc/comments to English (cleanAttachmentFiles, BaseAgent image-path resolver) per code style. - Add a resolver test for the relative-override escape fallback.
This commit is contained in:
parent
fcb488c567
commit
26dd8a37d5
@ -1347,6 +1347,15 @@ public class AgentGraphBuilder {
|
||||
return agentOverride;
|
||||
}
|
||||
if (hasWorkspace) {
|
||||
// Relative override resolves under the workspace root; reject any value
|
||||
// that escapes it via "../" so attachment/media/tool I/O stays contained.
|
||||
Path wsRoot = Paths.get(workspaceBase).toAbsolutePath().normalize();
|
||||
Path resolved = wsRoot.resolve(agentOverride).normalize();
|
||||
if (!resolved.startsWith(wsRoot)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Agent workspaceBasePath override must stay inside the workspace root: "
|
||||
+ resolved + " escapes " + wsRoot);
|
||||
}
|
||||
return Paths.get(workspaceBase).resolve(agentOverride).toString();
|
||||
}
|
||||
return agentOverride;
|
||||
|
||||
@ -1218,12 +1218,14 @@ public abstract class BaseAgent {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析图片文件的绝对路径。
|
||||
* Resolve the absolute path of an image file.
|
||||
* <p>
|
||||
* 上传文件的存储位置由 {@code ChatUploadLocationResolver} 按优先级解析:
|
||||
* Agent 的 workspaceBasePath → Workspace 的 basePath → 可配置默认目录
|
||||
* ({@code mateclaw.chat.upload.base-dir},默认 {@code data/chat-uploads})。
|
||||
* MCP 工具的工作目录可能不同,所以这里直接解析为绝对路径。
|
||||
* The storage location of uploaded files is resolved by
|
||||
* {@code ChatUploadLocationResolver} in priority order: the Agent's
|
||||
* workspaceBasePath → the Workspace's basePath → a configurable default
|
||||
* directory ({@code mateclaw.chat.upload.base-dir}, default
|
||||
* {@code data/chat-uploads}). An MCP tool's working directory may differ,
|
||||
* so this resolves directly to an absolute path.
|
||||
*/
|
||||
/**
|
||||
* 构建当前用户消息的 UserMessage(含 multimodal 图片注入)。
|
||||
|
||||
@ -1665,13 +1665,19 @@ public class ConversationService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理会话关联的附件文件
|
||||
* Clean up the attachment files associated with a conversation.
|
||||
* <p>
|
||||
* 遍历所有候选上传根(workspace/agent 感知根 + 默认根),逐一删除
|
||||
* 各根下该会话的附件目录。这样无论附件落在新的工作空间目录还是
|
||||
* 迁移前的默认目录,都能被清理。
|
||||
* Walks every candidate upload root (the workspace/agent-aware root plus the
|
||||
* default root) and deletes that conversation's attachment directory under
|
||||
* each, so attachments are removed whether they landed in the new workspace
|
||||
* directory or the pre-migration default directory.
|
||||
*/
|
||||
public void cleanAttachmentFiles(String conversationId) {
|
||||
if (conversationId == null || conversationId.isBlank()) {
|
||||
// A blank id would resolve to the upload root itself and wipe every
|
||||
// conversation's attachments — never walk/delete a bare root.
|
||||
return;
|
||||
}
|
||||
boolean cleanedAny = false;
|
||||
for (Path root : chatUploadLocationResolver.resolveCandidateUploadRoots(conversationId)) {
|
||||
Path dir;
|
||||
|
||||
@ -143,6 +143,23 @@ class ChatUploadLocationResolverTest {
|
||||
.resolve(ChatUploadLocationResolver.UPLOAD_SUBDIR));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("relative agent override that escapes the workspace root via ../ falls back to workspace basePath")
|
||||
void relativeAgentOverrideEscapingWorkspaceFallsBackToWorkspace() {
|
||||
stubConversation("c5b", 7L, 99L);
|
||||
Path wsBase = tempDir.resolve("ws-root");
|
||||
when(workspaceService.getById(7L)).thenReturn(workspace(7L, wsBase.toString()));
|
||||
// Relative override climbs out of the workspace root — resolveAgentBasePath
|
||||
// rejects it; the resolver falls back to the workspace basePath.
|
||||
when(agentService.getAgent(99L)).thenReturn(agent(99L, "../../escape", 7L));
|
||||
|
||||
ChatUploadLocationResolver r = resolver(tempDir);
|
||||
Path root = r.resolveUploadRoot("c5b");
|
||||
|
||||
assertThat(root).isEqualTo(wsBase.toAbsolutePath().normalize()
|
||||
.resolve(ChatUploadLocationResolver.UPLOAD_SUBDIR));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("candidate roots: workspace-scoped first, then default (dual-lookup order)")
|
||||
void candidateRootsOrderedScopedThenDefault() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user