From 7455ceecf88803be2e12c76937da301d7a964e80 Mon Sep 17 00:00:00 2001 From: shenghui kevin Date: Tue, 24 Feb 2026 10:58:39 -0800 Subject: [PATCH] fix(windows): skip unreliable dev comparison in fs-safe openVerifiedLocalFile On Windows, device IDs (dev) returned by handle.stat() and fs.lstat() may differ even for the same file, causing false-positive 'path-mismatch' errors when reading local media files. This fix introduces a statsMatch() helper that: - Always compares inode (ino) values - Skips device ID (dev) comparison on Windows where it's unreliable - Maintains full comparison on Unix platforms Fixes #25699 --- src/infra/fs-safe.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/infra/fs-safe.ts b/src/infra/fs-safe.ts index 7b6c648ee70..49604548a81 100644 --- a/src/infra/fs-safe.ts +++ b/src/infra/fs-safe.ts @@ -40,6 +40,23 @@ const OPEN_READ_FLAGS = fsConstants.O_RDONLY | (SUPPORTS_NOFOLLOW ? fsConstants. const ensureTrailingSep = (value: string) => (value.endsWith(path.sep) ? value : value + path.sep); +/** + * Compare file stats for identity verification. + * On Windows, device IDs (dev) are unreliable and may differ between + * handle.stat() and fs.lstat() for the same file. We skip dev comparison + * on Windows and rely solely on inode (ino) matching. + */ +function statsMatch(stat1: Stats, stat2: Stats): boolean { + if (stat1.ino !== stat2.ino) { + return false; + } + // On Windows, dev values are unreliable across different stat sources + if (process.platform !== "win32" && stat1.dev !== stat2.dev) { + return false; + } + return true; +} + async function openVerifiedLocalFile(filePath: string): Promise { let handle: FileHandle; try { @@ -62,13 +79,13 @@ async function openVerifiedLocalFile(filePath: string): Promise if (!stat.isFile()) { throw new SafeOpenError("not-file", "not a file"); } - if (stat.ino !== lstat.ino || stat.dev !== lstat.dev) { + if (!statsMatch(stat, lstat)) { throw new SafeOpenError("path-mismatch", "path changed during read"); } const realPath = await fs.realpath(filePath); const realStat = await fs.stat(realPath); - if (stat.ino !== realStat.ino || stat.dev !== realStat.dev) { + if (!statsMatch(stat, realStat)) { throw new SafeOpenError("path-mismatch", "path mismatch"); }