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
This commit is contained in:
shenghui kevin
2026-02-24 10:58:39 -08:00
committed by Peter Steinberger
parent 3c95f89662
commit 7455ceecf8

View File

@@ -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<SafeOpenResult> {
let handle: FileHandle;
try {
@@ -62,13 +79,13 @@ async function openVerifiedLocalFile(filePath: string): Promise<SafeOpenResult>
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");
}