From 6b7ba4af4035149a794fa88f66da1f4c10db61c0 Mon Sep 17 00:00:00 2001 From: Aniruddha Adak Date: Tue, 8 Sep 2026 12:01:29 +0530 Subject: [PATCH] fix(tools): reject parent-directory segments in Claude memory paths --- packages/tools/src/claude-memory.test.ts | 26 ++++++++++++++++++++++++ packages/tools/src/claude-memory.ts | 14 ++++++++----- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/tools/src/claude-memory.test.ts b/packages/tools/src/claude-memory.test.ts index 7337441a..37bd647d 100644 --- a/packages/tools/src/claude-memory.test.ts +++ b/packages/tools/src/claude-memory.test.ts @@ -210,3 +210,29 @@ describe("ClaudeMemoryTool str_replace replacement literalness", () => { expect(stored).toContain(`price is ${dollarSequence} today`) }) }) + +describe("ClaudeMemoryTool path traversal", () => { + let tool: ClaudeMemoryTool + + beforeEach(() => { + documentsListMock.mockReset() + documentsGetMock.mockReset() + addMock.mockReset() + mockDocument(FILE_CONTENT) + tool = new ClaudeMemoryTool("test-api-key") + }) + + it.each(["/memories/..", "/memories/foo/..", "/memories/../secrets.txt"])( + "rejects parent-directory path %s", + async (path) => { + const result = await tool.handleCommand({ + command: "view", + path, + }) + + expect(result.success).toBe(false) + expect(result.error).toContain("Invalid path") + expect(documentsListMock).not.toHaveBeenCalled() + }, + ) +}) diff --git a/packages/tools/src/claude-memory.ts b/packages/tools/src/claude-memory.ts index 867c4d82..70a2b257 100644 --- a/packages/tools/src/claude-memory.ts +++ b/packages/tools/src/claude-memory.ts @@ -805,11 +805,15 @@ export class ClaudeMemoryTool { * Validate that path starts with /memories for security */ private isValidPath(path: string): boolean { - return ( - (path.startsWith("/memories/") || path === "/memories") && - !path.includes("../") && - !path.includes("..\\") - ) + if (!(path.startsWith("/memories/") || path === "/memories")) { + return false + } + if (path.includes("..\\")) { + return false + } + // Reject any parent-directory segment, including trailing "/.." which + // the previous "../" substring check missed (e.g. "/memories/.."). + return !path.split("/").some((segment) => segment === "..") } }