diff --git a/src/infra/fs-safe.test.ts b/src/infra/fs-safe.test.ts index 2dea108b0da..a248729a68a 100644 --- a/src/infra/fs-safe.test.ts +++ b/src/infra/fs-safe.test.ts @@ -172,7 +172,7 @@ describe("fs-safe", () => { const resolved = await resolveOpenedFileRealPathForHandle(handle, originalPath); - expect(resolved).toBe(movedPath); + await expect(fs.realpath(movedPath)).resolves.toBe(resolved); await expect(handle.readFile({ encoding: "utf8" })).resolves.toBe("inside"); } finally { await handle.close().catch(() => {}); diff --git a/src/infra/fs-safe.ts b/src/infra/fs-safe.ts index 8bc90a22016..ea6fe2c7d16 100644 --- a/src/infra/fs-safe.ts +++ b/src/infra/fs-safe.ts @@ -417,6 +417,7 @@ export async function resolveOpenedFileRealPathForHandle( handle: FileHandle, ioPath: string, ): Promise { + const handleStat = await handle.stat(); const fdCandidates = process.platform === "linux" ? [`/proc/self/fd/${handle.fd}`, `/dev/fd/${handle.fd}`] @@ -425,22 +426,74 @@ export async function resolveOpenedFileRealPathForHandle( : [`/dev/fd/${handle.fd}`]; for (const fdPath of fdCandidates) { try { - return await fs.realpath(fdPath); + const fdRealPath = await fs.realpath(fdPath); + const fdRealStat = await fs.stat(fdRealPath); + if (sameFileIdentity(handleStat, fdRealStat)) { + return fdRealPath; + } } catch { // try next fd path } } try { - return await fs.realpath(ioPath); + const ioRealPath = await fs.realpath(ioPath); + const ioRealStat = await fs.stat(ioRealPath); + if (sameFileIdentity(handleStat, ioRealStat)) { + return ioRealPath; + } } catch (err) { if (!isNotFoundPathError(err)) { throw err; } } + const parentResolved = await resolveOpenedFileRealPathFromParent(handleStat, ioPath); + if (parentResolved) { + return parentResolved; + } throw new SafeOpenError("path-mismatch", "unable to resolve opened file path"); } +async function resolveOpenedFileRealPathFromParent( + handleStat: Stats, + ioPath: string, +): Promise { + let parentReal: string; + try { + parentReal = await fs.realpath(path.dirname(ioPath)); + } catch (err) { + if (isNotFoundPathError(err)) { + return null; + } + throw err; + } + + let entries: string[]; + try { + entries = await fs.readdir(parentReal); + } catch (err) { + if (isNotFoundPathError(err)) { + return null; + } + throw err; + } + + for (const entry of entries.toSorted()) { + const candidatePath = path.join(parentReal, entry); + try { + const candidateStat = await fs.lstat(candidatePath); + if (candidateStat.isFile() && sameFileIdentity(handleStat, candidateStat)) { + return await fs.realpath(candidatePath); + } + } catch (err) { + if (!isNotFoundPathError(err)) { + throw err; + } + } + } + return null; +} + export async function openWritableFileWithinRoot(params: { rootDir: string; relativePath: string;