Skip to content

Resolve junctions and mount points on Windows - #288

Open
bgrainger wants to merge 2 commits into
github:mainfrom
bgrainger:fix-windows-junction-resolve
Open

bgrainger wants to merge 2 commits into
github:mainfrom
bgrainger:fix-windows-junction-resolve

Conversation

@bgrainger

@bgrainger bgrainger commented Nov 14, 2026 •

Copy link
Copy Markdown

Fixes #287

Go 1.23 stopped treating Windows junctions and mount points as symlinks (golang/go#61893), so filepath.EvalSymlinks fails below them and returns a trailing one unresolved.

On Windows, resolve paths with GetFinalPathNameByHandle instead. Other platforms are unchanged.

  • Repos under a junction or mounted volume (e.g. a Dev Drive) work again.
  • A junction pointing out of the repo is rejected, including as the last path element.
  • Long absolute paths get the \\?\ prefix, as os.Open does, so they still resolve without long-path opt-in.
  • golang.org/x/sys becomes a direct dependency.
  • A volume with no DOS name still fails closed with an error.

Testing

New Windows-only tests create junctions with mklink /J. On main, four of the five containment cases and the repo-behind-junction case fail. CI is Linux-only, so these don't run there.

Also ran the built binary on a repo under a folder-mounted volume: fails on main, works with this change.

On Windows, go test ./... has the same 6 failures with and without this change (TestMigrateLocalActions_UnparseableWorkflowIsAFinding, TestPublicFallbackCredential).

🤖 Generated with Claude Code

Since Go 1.23, Windows junctions and mount points are no longer
reported as symlinks, so filepath.EvalSymlinks fails with
ERROR_PATH_NOT_FOUND below one and returns a trailing one unresolved.
That broke ValidatePathWithinRoot for any repo under a junction or
folder-mounted volume (e.g. a Dev Drive).

Resolve paths with GetFinalPathNameByHandle on Windows instead.

Fixes github#287

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@bgrainger
bgrainger requested a review from a team as a code owner November 14, 2026 17:56
Copilot AI dynamic review requested due to automatic review settings November 14, 2026 17:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Windows long-path inputs can still fail validation, and Ubuntu-only CI leaves the Windows junction-containment tests unexecuted.

2 open issues
What changed in this PR

Updates repository-path validation to resolve Windows junctions and mounted volumes, addressing #287 while leaving other platforms unchanged.

Changes:

  • Use handle-based Windows path resolution for repository containment checks.
  • Add Windows tests for junction traversal, escape rejection, and path-prefix handling.
  • Promote golang.org/x/sys to a direct dependency.
File Description
internal/​workflowfile/​workflowfile.go Route containment checks through the platform-specific resolver.
internal/​workflowfile/​evalsymlinks_windows.go Resolve Windows paths using GetFinalPathNameByHandle.
internal/​workflowfile/​evalsymlinks_windows_test.go Test junction resolution, containment, and prefix trimming.
internal/​workflowfile/​evalsymlinks_other.go Preserve filepath.EvalSymlinks behavior outside Windows.
go.mod Declare golang.org/x/sys as a direct dependency.

Review effort: Max (dynamic)

// as non-directories, so it fails with ERROR_PATH_NOT_FOUND below one and
// returns a trailing one unresolved (golang/go#61893).
func evalSymlinks(path string) (string, error) {
p, err := windows.UTF16PtrFromString(path)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6b81a0d. Absolute paths of 248+ characters now get the \\?\ prefix (\\?\UNC\ for UNC) before CreateFile, as os.Open does. Added a long-path test and a prefix table.

I couldn't reproduce the failure here (long paths are enabled), so the test covers the prefixed open, not the failure.

})
}

func TestValidatePathWithinRoot_Junction(t *testing.T) {
filepath.EvalSymlinks went through os.Lstat, which switches long paths
to the \?\ form. Calling CreateFile directly lost that, so without
long-path opt-in a path past MAX_PATH could fail validation.

Prefix long absolute paths the same way.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.

Windows: "path not found" when the repo is under a junction or mounted volume

2 participants