fix(server): expand ~ in file-explorer scoped paths
Assistant markdown images with ~-prefixed paths hit ENOENT because path.resolve doesn't expand ~, leaving paths like workspaceRoot/~/rest-of-path that never resolve. Reuse the existing resolvePathFromBase helper so tilde paths inside the workspace load instead of getting stuck on the image spinner; the sandbox check still rejects tilde paths that resolve outside the workspace.
This commit is contained in:
@@ -4,6 +4,10 @@ import path from "node:path";
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { listDirectoryEntries, readExplorerFile } from "./service.js";
|
import { listDirectoryEntries, readExplorerFile } from "./service.js";
|
||||||
|
|
||||||
|
async function createHomeTempDir(prefix: string): Promise<string> {
|
||||||
|
return mkdtemp(path.join(os.homedir(), prefix));
|
||||||
|
}
|
||||||
|
|
||||||
async function createTempDir(prefix: string): Promise<string> {
|
async function createTempDir(prefix: string): Promise<string> {
|
||||||
return mkdtemp(path.join(os.tmpdir(), prefix));
|
return mkdtemp(path.join(os.tmpdir(), prefix));
|
||||||
}
|
}
|
||||||
@@ -97,6 +101,41 @@ describe("file explorer service", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("expands a ~ prefix in relative paths against the user home directory", async () => {
|
||||||
|
const root = await createHomeTempDir(".paseo-file-explorer-home-");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filePath = path.join(root, "sample.txt");
|
||||||
|
await writeFile(filePath, "hello from home\n", "utf-8");
|
||||||
|
|
||||||
|
const tildePath = `~/${path.relative(os.homedir(), filePath)}`;
|
||||||
|
const result = await readExplorerFile({
|
||||||
|
root,
|
||||||
|
relativePath: tildePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.kind).toBe("text");
|
||||||
|
expect(result.content).toBe("hello from home\n");
|
||||||
|
} finally {
|
||||||
|
await rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects ~-prefixed paths that resolve outside the workspace", async () => {
|
||||||
|
const root = await mkdtemp(path.join(os.tmpdir(), "paseo-file-explorer-outside-home-"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await expect(
|
||||||
|
readExplorerFile({
|
||||||
|
root,
|
||||||
|
relativePath: "~/some/file.txt",
|
||||||
|
}),
|
||||||
|
).rejects.toThrow("Access outside of workspace is not allowed");
|
||||||
|
} finally {
|
||||||
|
await rm(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects symlinked files that resolve outside the workspace", async () => {
|
it("rejects symlinked files that resolve outside the workspace", async () => {
|
||||||
const root = await createTempDir("paseo-file-explorer-");
|
const root = await createTempDir("paseo-file-explorer-");
|
||||||
const outsideRoot = await createTempDir("paseo-file-explorer-outside-");
|
const outsideRoot = await createTempDir("paseo-file-explorer-outside-");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { promises as fs } from "fs";
|
import { promises as fs } from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { resolvePathFromBase } from "../path-utils.js";
|
||||||
|
|
||||||
export type ExplorerEntryKind = "file" | "directory";
|
export type ExplorerEntryKind = "file" | "directory";
|
||||||
export type ExplorerFileKind = "text" | "image" | "binary";
|
export type ExplorerFileKind = "text" | "image" | "binary";
|
||||||
@@ -207,7 +208,7 @@ export async function getDownloadableFileInfo({ root, relativePath }: ReadFilePa
|
|||||||
|
|
||||||
async function resolveScopedPath({ root, relativePath = "." }: ScopedPathParams): Promise<string> {
|
async function resolveScopedPath({ root, relativePath = "." }: ScopedPathParams): Promise<string> {
|
||||||
const normalizedRoot = path.resolve(root);
|
const normalizedRoot = path.resolve(root);
|
||||||
const requestedPath = path.resolve(normalizedRoot, relativePath);
|
const requestedPath = resolvePathFromBase(normalizedRoot, relativePath);
|
||||||
const relative = path.relative(normalizedRoot, requestedPath);
|
const relative = path.relative(normalizedRoot, requestedPath);
|
||||||
|
|
||||||
if (relative !== "" && (relative.startsWith("..") || path.isAbsolute(relative))) {
|
if (relative !== "" && (relative.startsWith("..") || path.isAbsolute(relative))) {
|
||||||
|
|||||||
Reference in New Issue
Block a user