diff --git a/packages/coding-agent/src/internal-urls/memory-protocol.ts b/packages/coding-agent/src/internal-urls/memory-protocol.ts index 95823fe0f..4653b7514 100644 --- a/packages/coding-agent/src/internal-urls/memory-protocol.ts +++ b/packages/coding-agent/src/internal-urls/memory-protocol.ts @@ -88,15 +88,13 @@ export function splitMemoryGlobPattern(input: string): MemoryGlobPattern { throw toMemoryValidationError(error); } - const decodedSegments = relativePath.split("/"); - const firstGlobIndex = decodedSegments.findIndex(segment => - ["*", "?", "[", "{"].some(char => segment.includes(char)), - ); + const rawSegments = rawPathname.replace(/^\//, "").split("/"); + const firstGlobIndex = rawSegments.findIndex(segment => ["*", "?", "[", "{"].some(char => segment.includes(char))); if (firstGlobIndex === -1) { throw new Error(`memory:// URL does not contain a glob pattern: ${input}`); } - const rawSegments = rawPathname.replace(/^\//, "").split("/"); + const decodedSegments = relativePath.split("/"); const rawBasePath = rawSegments.slice(0, firstGlobIndex).join("/") || "."; return { baseUrl: `memory://${namespace}/${rawBasePath}`, diff --git a/packages/coding-agent/src/tools/glob.ts b/packages/coding-agent/src/tools/glob.ts index 6a15e538e..494b186d4 100644 --- a/packages/coding-agent/src/tools/glob.ts +++ b/packages/coding-agent/src/tools/glob.ts @@ -192,7 +192,9 @@ export class GlobTool implements AgentTool { if (!resource.sourcePath) { throw new ToolError(`Cannot find internal URL without a backing file: ${memoryGlob.baseUrl}`); } - normalizedPatterns.push(path.join(resource.sourcePath, memoryGlob.globPattern)); + normalizedPatterns.push( + path.join(resource.sourcePath.replace(/[*?[{]/g, "[$&]"), memoryGlob.globPattern), + ); continue; } const resource = await internalRouter.resolve(rawPattern, { diff --git a/packages/coding-agent/test/internal-urls/memory-protocol.test.ts b/packages/coding-agent/test/internal-urls/memory-protocol.test.ts index fddfe96e7..d3a3821fb 100644 --- a/packages/coding-agent/test/internal-urls/memory-protocol.test.ts +++ b/packages/coding-agent/test/internal-urls/memory-protocol.test.ts @@ -284,6 +284,21 @@ describe("MemoryProtocolHandler", () => { }); }); + it("resolves encoded literal glob characters before the wildcard boundary", async () => { + await withMemoryFixture(async ({ cwd, memoryRoot }) => { + const encodedLiteralDir = path.join(memoryRoot, "skills", "[demo]"); + await fs.mkdir(encodedLiteralDir, { recursive: true }); + await Bun.write(path.join(encodedLiteralDir, "SKILL.md"), "encoded literal directory"); + + const result = await createGlobTool(cwd).execute("memory-encoded-literal-glob", { + path: "memory://root/skills/%5Bdemo%5D/*.md", + }); + + expect(result.details?.files).toHaveLength(1); + expect(result.details?.files?.[0]).toEndWith("/skills/[demo]/SKILL.md"); + }); + }); + it.each(["memory://root/skills/**/../*.md", "memory://root/skills/**/%2e%2e/*.md"])( "rejects traversal in a memory glob suffix: %s", async pattern => {