fix(coding-agent): preserve encoded memory glob bases

This commit is contained in:
usr-bin-roygbiv
2026-07-26 21:33:13 +00:00
parent 6b65fd2996
commit 49d5145fff
3 changed files with 21 additions and 6 deletions
@@ -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}`,
+3 -1
View File
@@ -192,7 +192,9 @@ export class GlobTool implements AgentTool<typeof findSchema, GlobToolDetails> {
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, {
@@ -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 => {