fix: prevent local absolute paths from being treated as remote URLs (#2397)

Add filepath.IsAbs guard to IsRemote and Parse to prevent Windows drive
letter paths (e.g., C:\path) from being misinterpreted as remote URLs
by go-getter's url.Parse, which prepends file:// to drive letter paths.

Fixes #2384

Signed-off-by: Aditya Menon <amenon@canarytechnologies.com>
This commit is contained in:
Aditya Menon 2026-02-12 12:22:51 +05:30 committed by GitHub
parent 3a0703425f
commit 2f8b9cbdfb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 132 additions and 0 deletions

View File

@ -102,6 +102,9 @@ type Source struct {
}
func IsRemote(goGetterSrc string) bool {
if filepath.IsAbs(goGetterSrc) {
return false
}
if _, err := Parse(goGetterSrc); err != nil {
return false
}
@ -122,6 +125,13 @@ func Parse(goGetterSrc string) (*Source, error) {
}
}
// Local absolute paths (e.g., C:\path on Windows, /path on Unix)
// are not remote URLs. Reject them before go-getter's url.Parse
// can misinterpret Windows drive letters as URL schemes.
if filepath.IsAbs(goGetterSrc) {
return nil, InvalidURLError{err: fmt.Sprintf("parse url: local absolute path is not a remote URL: %s", goGetterSrc)}
}
u, err := url.Parse(goGetterSrc)
if err != nil {
return nil, InvalidURLError{err: fmt.Sprintf("parse url: %v", err)}

View File

@ -356,6 +356,54 @@ func TestRemote_S3(t *testing.T) {
}
}
func TestIsRemote(t *testing.T) {
testcases := []struct {
name string
input string
expected bool
}{
{
name: "git remote URL",
input: "git::https://github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0",
expected: true,
},
{
name: "s3 remote URL",
input: "s3://helm-s3-values-example/values.yaml",
expected: true,
},
{
name: "https remote URL",
input: "https://example.com/values.yaml",
expected: true,
},
{
name: "relative path",
input: "relative/path/to/file.yaml",
expected: false,
},
{
name: "parent-relative path",
input: "../services/values.yaml",
expected: false,
},
{
name: "unix absolute path",
input: "/absolute/path/to/file.yaml",
expected: false,
},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
result := IsRemote(tt.input)
if result != tt.expected {
t.Errorf("IsRemote(%q) = %v, want %v", tt.input, result, tt.expected)
}
})
}
}
func TestParse(t *testing.T) {
testcases := []struct {
name string
@ -372,6 +420,11 @@ func TestParse(t *testing.T) {
input: "raw/incubator",
err: "parse url: missing scheme - probably this is a local file path? raw/incubator",
},
{
name: "unix absolute path",
input: "/absolute/path/to/file.yaml",
err: "parse url: local absolute path is not a remote URL: /absolute/path/to/file.yaml",
},
{
name: "git scheme",
input: "git::https://github.com/stakater/Forecastle.git@deployments/kubernetes/chart/forecastle?ref=v1.0.54",

View File

@ -0,0 +1,69 @@
//go:build windows
package remote
import (
"testing"
)
func TestIsRemote_Windows(t *testing.T) {
testcases := []struct {
name string
input string
expected bool
}{
{
name: "windows drive letter path",
input: `C:\project\services\values.yaml`,
expected: false,
},
{
name: "windows UNC path",
input: `\\server\share\path\values.yaml`,
expected: false,
},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
result := IsRemote(tt.input)
if result != tt.expected {
t.Errorf("IsRemote(%q) = %v, want %v", tt.input, result, tt.expected)
}
})
}
}
func TestParse_Windows(t *testing.T) {
testcases := []struct {
name string
input string
err string
}{
{
name: "windows drive letter path",
input: `C:\project\services\values.yaml`,
err: `parse url: local absolute path is not a remote URL: C:\project\services\values.yaml`,
},
{
name: "windows UNC path",
input: `\\server\share\path\values.yaml`,
err: `parse url: local absolute path is not a remote URL: \\server\share\path\values.yaml`,
},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
_, err := Parse(tt.input)
if err == nil {
t.Fatalf("Parse(%q) expected error, got nil", tt.input)
}
if _, ok := err.(InvalidURLError); !ok {
t.Fatalf("Parse(%q) expected InvalidURLError, got %T: %v", tt.input, err, err)
}
if err.Error() != tt.err {
t.Errorf("Parse(%q) error = %q, want %q", tt.input, err.Error(), tt.err)
}
})
}
}