diff --git a/mateclaw-server/src/main/java/vip/mate/memory/controller/DreamController.java b/mateclaw-server/src/main/java/vip/mate/memory/controller/DreamController.java index 4a6aec6a..285c9067 100644 --- a/mateclaw-server/src/main/java/vip/mate/memory/controller/DreamController.java +++ b/mateclaw-server/src/main/java/vip/mate/memory/controller/DreamController.java @@ -129,7 +129,7 @@ public class DreamController { @PathVariable Long reportId, @PathVariable String key, @RequestBody Map body) { - // P2-5: Validate reportId belongs to this agent (0 = direct edit, skip validation) + // Validate reportId belongs to this agent (0 = direct edit from MemoryBrowser) if (reportId != 0L) { DreamReportEntity report = dreamReportMapper.selectOne( new LambdaQueryWrapper() @@ -140,11 +140,16 @@ public class DreamController { return R.fail("Report not found or does not belong to this agent"); } } + // Validate key is an existing section in MEMORY.md (prevent arbitrary edits) + String decodedKey = java.net.URLDecoder.decode(key, java.nio.charset.StandardCharsets.UTF_8); + if (!hilService.sectionExists(agentId, decodedKey)) { + return R.fail("Section '" + decodedKey + "' not found in MEMORY.md"); + } String newContent = body.get("content"); if (newContent == null || newContent.isBlank()) { return R.fail("content is required"); } - hilService.editMemoryEntry(agentId, key, newContent); + hilService.editMemoryEntry(agentId, decodedKey, newContent); return R.ok(null); } diff --git a/mateclaw-server/src/main/java/vip/mate/memory/service/MemoryHilService.java b/mateclaw-server/src/main/java/vip/mate/memory/service/MemoryHilService.java index 6289329f..1d38f130 100644 --- a/mateclaw-server/src/main/java/vip/mate/memory/service/MemoryHilService.java +++ b/mateclaw-server/src/main/java/vip/mate/memory/service/MemoryHilService.java @@ -64,4 +64,14 @@ public class MemoryHilService { eventPublisher.publishEvent(new MemoryWriteEvent(agentId, "MEMORY.md", "user-edit", newContent)); log.info("[HiL] User edited MEMORY.md section '{}' for agent={}", key, agentId); } + + /** + * Check if a section heading exists in MEMORY.md. + * Used by DreamController to validate edit key before allowing write. + */ + public boolean sectionExists(Long agentId, String key) { + WorkspaceFileEntity file = workspaceFileService.getFile(agentId, "MEMORY.md"); + if (file == null || file.getContent() == null) return false; + return file.getContent().contains("## " + key); + } }