Skip to content

Fix Windows path detection in cleanPathWithBase#644

Open
owenthereal wants to merge 1 commit into
pkg:masterfrom
owenthereal:master
Open

Fix Windows path detection in cleanPathWithBase#644
owenthereal wants to merge 1 commit into
pkg:masterfrom
owenthereal:master

Conversation

@owenthereal
Copy link
Copy Markdown

Summary

Fix cleanPathWithBase to use filepath.IsAbs on the original path before converting to forward slashes. This correctly handles Windows paths like C:\foo which filepath.IsAbs recognizes but path.IsAbs (after ToSlash conversion to C:/foo) does not.

Problem

When using WithStartDirectory on Windows, absolute paths like C:\Users\foo are incorrectly treated as relative paths because:

  1. filepath.ToSlash(filepath.Clean("C:\\Users\\foo"))C:/Users/foo
  2. path.IsAbs("C:/Users/foo")false (only checks for / prefix)
  3. Result: path gets joined with start directory, causing doubled paths

Solution

Check filepath.IsAbs(p) on the original path before the ToSlash conversion. filepath.IsAbs is platform-aware and correctly identifies Windows absolute paths.

Related

This fix resolves Windows SFTP path handling issues in owenthereal/upterm#440

Use filepath.IsAbs on the original path before converting to forward
slashes. This correctly handles Windows paths like "C:\foo" which
filepath.IsAbs recognizes but path.IsAbs (after ToSlash conversion
to "C:/foo") does not.
@puellanivis
Copy link
Copy Markdown
Collaborator

Remote paths to windows servers require absolute paths to be encoded as /C:/path/to/file.

This is not a bug.

@puellanivis puellanivis reopened this Jan 13, 2026
@puellanivis
Copy link
Copy Markdown
Collaborator

puellanivis commented Jan 13, 2026

I keep thinking I’m missing something here.

I’m thinking I need this explained better. This code is only called from within the RequestServer, and only on paths coming from a remote client. Where, like I said, any absolute path coming from a client to a windows server must be encoded as /c:/path/to/file, but I can’t shake the feeling that I’m missing something that I should be able to see…

@owenthereal
Copy link
Copy Markdown
Author

The premise that "this code is only called from within the RequestServer, and only on paths coming from a remote client" isn't quite right. cleanPath is reachable through public API surface that takes Go-side strings:

  • NewRequest(method, path) (request.go:160) runs cleanPath(path) on whatever the caller passes.
  • WithStartDirectory(dir) (request-server.go:55) runs cleanPath(dir) on a Go-side string.

On Windows, a Go program calling either of these with a native path — e.g. os.UserHomeDir() returning C:\Users\foo, or any path produced by filepath.Join — sends an absolute Windows path through. With the original code on Windows:

cleanPathWithBase("/C:/Users/foo", "C:\\Users\\foo\\test.txt")
  = path.Join("/C:/Users/foo", "C:/Users/foo/test.txt")
  = "/C:/Users/foo/C:/Users/foo/test.txt"   ← doubled

That happens because path.IsAbs("C:/Users/foo/test.txt") is false (it only checks for a leading /), so the path goes through path.Join instead of being returned as-is. filepath.IsAbs on the original (pre-ToSlash) string is platform-aware and recognizes the Windows absolute path correctly, which is what this change uses.

Agreed that the wire form for SFTP-to-Windows is /C:/path, and that conformant clients send canonical paths. But (a) the public Go-side API takes native paths in practice, and (b) being lenient on input here is harmless — on Unix, filepath.IsAbs and path.IsAbs agree on these inputs, so non-Windows behavior is unchanged.

Happy to add a Windows test that exercises this if that would help.

@puellanivis
Copy link
Copy Markdown
Collaborator

Yeah, a Windows-specific test would be great.

🤔 It looks like you’re right, there are a few API positions where we’re calling this on a string that users might naturally expect to use a local filepath, rather than a POSIX path. It might be a good idea to instead split these uses into separate cleanRemotePath and cleanLocalPath… if we don’t leave any plain cleanPath around, then we can’t accidentally leave a path undecided as to if it is expected to be remote or local.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants