From e98a25134a6af1fd452e98a9e37a8a193181b935 Mon Sep 17 00:00:00 2001 From: Kartik Suryavanshi <158498247+KartikSuryavanshi@users.noreply.github.com> Date: Thu, 25 Jun 2026 12:09:55 +0530 Subject: [PATCH] fix(sympath): skip broken symlinks instead of erroring When sympath.Walk encounters a broken symlink whose target does not exist, silently skip it instead of returning an error. Other EvalSymlinks errors (e.g. permission denied, symlink loops) are still propagated. Broken symlinks cannot contribute useful content, and this prevents errors when they are listed in .helmignore patterns. Fixes #13284 Signed-off-by: Kartik Suryavanshi <158498247+KartikSuryavanshi@users.noreply.github.com> --- internal/chart/v3/loader/load_test.go | 50 +++++++++++++++++++++++++++ internal/sympath/walk.go | 10 ++++-- pkg/chart/v2/loader/load_test.go | 50 +++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index de9219a1b..9b8762e51 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -90,6 +90,56 @@ func TestLoadDirWithSymlink(t *testing.T) { verifyDependenciesLock(t, c) } +func TestLoadDirWithBrokenSymlinkInHelmignore(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("symlink tests require unix") + } + + tmpDir := t.TempDir() + + // Create minimal chart structure + chartYAML := `apiVersion: v2 +name: test +version: 0.1.0 +` + valuesYAML := `{} +` + tpl := `config: {} +` + if err := os.WriteFile(filepath.Join(tmpDir, "Chart.yaml"), []byte(chartYAML), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(tmpDir, "values.yaml"), []byte(valuesYAML), 0644); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(tmpDir, "templates"), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(tmpDir, "templates", "config.yaml"), []byte(tpl), 0644); err != nil { + t.Fatal(err) + } + + // Create a broken symlink in templates/ + brokenLink := filepath.Join(tmpDir, "templates", "broken") + if err := os.Symlink("nonexistent", brokenLink); err != nil { + t.Fatal(err) + } + + // Add the broken symlink to .helmignore + if err := os.WriteFile(filepath.Join(tmpDir, ".helmignore"), []byte("templates/broken\n"), 0644); err != nil { + t.Fatal(err) + } + + // Loading should succeed despite the broken symlink + l, err := Loader(tmpDir) + if err != nil { + t.Fatal(err) + } + if _, err := l.Load(); err != nil { + t.Fatalf("loading chart with broken symlink in .helmignore should not fail: %s", err) + } +} + func TestBomTestData(t *testing.T) { testFiles := []string{"frobnitz_with_bom/.helmignore", "frobnitz_with_bom/templates/template.tpl", "frobnitz_with_bom/Chart.yaml"} for _, file := range testFiles { diff --git a/internal/sympath/walk.go b/internal/sympath/walk.go index 812bb68ce..4aca38693 100644 --- a/internal/sympath/walk.go +++ b/internal/sympath/walk.go @@ -21,7 +21,6 @@ limitations under the License. package sympath import ( - "fmt" "log/slog" "os" "path/filepath" @@ -68,7 +67,14 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { if IsSymlink(info) { resolved, err := filepath.EvalSymlinks(path) if err != nil { - return fmt.Errorf("error evaluating symlink %s: %w", path, err) + if os.IsNotExist(err) { + // If a symlink's target does not exist (broken symlink), + // silently skip it. Broken symlinks do not contribute + // content and should not cause errors, especially when + // they match .helmignore patterns. + return nil + } + return err } // This log message is to highlight a symlink that is being used within a chart, symlinks can be used for nefarious reasons. slog.Info("found symbolic link in path. Contents of linked file included and used", "path", path, "resolved", resolved) diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index be7041386..56954b16a 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -90,6 +90,56 @@ func TestLoadDirWithSymlink(t *testing.T) { verifyDependenciesLock(t, c) } +func TestLoadDirWithBrokenSymlinkInHelmignore(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("symlink tests require unix") + } + + tmpDir := t.TempDir() + + // Create minimal chart structure + chartYAML := `apiVersion: v2 +name: test +version: 0.1.0 +` + valuesYAML := `{} +` + tpl := `config: {} +` + if err := os.WriteFile(filepath.Join(tmpDir, "Chart.yaml"), []byte(chartYAML), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(tmpDir, "values.yaml"), []byte(valuesYAML), 0644); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(tmpDir, "templates"), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(tmpDir, "templates", "config.yaml"), []byte(tpl), 0644); err != nil { + t.Fatal(err) + } + + // Create a broken symlink in templates/ + brokenLink := filepath.Join(tmpDir, "templates", "broken") + if err := os.Symlink("nonexistent", brokenLink); err != nil { + t.Fatal(err) + } + + // Add the broken symlink to .helmignore + if err := os.WriteFile(filepath.Join(tmpDir, ".helmignore"), []byte("templates/broken\n"), 0644); err != nil { + t.Fatal(err) + } + + // Loading should succeed despite the broken symlink + l, err := Loader(tmpDir) + if err != nil { + t.Fatal(err) + } + if _, err := l.Load(); err != nil { + t.Fatalf("loading chart with broken symlink in .helmignore should not fail: %s", err) + } +} + func TestBomTestData(t *testing.T) { testFiles := []string{"frobnitz_with_bom/.helmignore", "frobnitz_with_bom/templates/template.tpl", "frobnitz_with_bom/Chart.yaml"} for _, file := range testFiles {