From 3dc87fe6363e54c18c26398f94b62ff0bc0b9836 Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Sat, 27 Jun 2026 11:13:39 +0900 Subject: [PATCH] path: fix win32 normalize false-positive on reserved names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `path.win32.normalize()` checked for Windows reserved device names (CON, NUL, PRN, LPT1, etc.) without first ensuring the path actually contained a colon. When no colon was present, `indexOf(':')` returned -1 and `isWindowsReservedName()` sliced off the last character instead of slicing up to a colon, so any filename equal to a reserved name plus one trailing character was wrongly treated as a device and prefixed with `.\` — e.g. `normalize('CONx')` returned `.\CONx` instead of `CONx`. Guard the check with `colonIndex !== -1`, matching the other reserved name call sites which are already guarded by `colonIndex > 0`. Signed-off-by: Daijiro Wachi --- lib/path.js | 2 +- test/parallel/test-path-normalize.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/path.js b/lib/path.js index 63b037cddfb986..253f5dc6165804 100644 --- a/lib/path.js +++ b/lib/path.js @@ -471,7 +471,7 @@ const win32 = { } while ((index = StringPrototypeIndexOf(path, ':', index + 1)) !== -1); } const colonIndex = StringPrototypeIndexOf(path, ':'); - if (isWindowsReservedName(path, colonIndex)) { + if (colonIndex !== -1 && isWindowsReservedName(path, colonIndex)) { return `.\\${device ?? ''}${tail}`; } if (device === undefined) { diff --git a/test/parallel/test-path-normalize.js b/test/parallel/test-path-normalize.js index 8b537676dbf45d..c0e7309602b60b 100644 --- a/test/parallel/test-path-normalize.js +++ b/test/parallel/test-path-normalize.js @@ -69,6 +69,17 @@ assert.strictEqual(path.win32.normalize('//server/share/dir/../../../?/D:/file') assert.strictEqual(path.win32.normalize('//server/goodshare/../badshare/file'), '\\\\server\\goodshare\\badshare\\file'); +// A path is only a Windows reserved device name when the reserved name is +// followed by a colon. A name that merely starts with a reserved name (and has +// no colon) must be left untouched and not be prefixed with `.\`. +assert.strictEqual(path.win32.normalize('CONx'), 'CONx'); +assert.strictEqual(path.win32.normalize('NULs'), 'NULs'); +assert.strictEqual(path.win32.normalize('LPT1x'), 'LPT1x'); +assert.strictEqual(path.win32.normalize('PRNzzz'), 'PRNzzz'); +assert.strictEqual(path.win32.normalize('CON'), 'CON'); +// With a trailing colon the reserved-name handling still applies. +assert.strictEqual(path.win32.normalize('CON:'), '.\\CON:.'); + assert.strictEqual(path.posix.normalize('./fixtures///b/../b/c.js'), 'fixtures/b/c.js'); assert.strictEqual(path.posix.normalize('/foo/../../../bar'), '/bar');