Duplicate plugin entries in Helm
Description
In Helm before versions 2.16.11 and 3.3.2, a Helm plugin can contain duplicates of the same entry, with the last one always used. If a plugin is compromised, this lowers the level of access that an attacker needs to modify a plugin's install hooks, causing a local execution attack. To perform this attack, an attacker must have write access to the git repository or plugin archive (.tgz) while being downloaded (which can occur during a MITM attack on a non-SSL connection). This issue has been patched in Helm 2.16.11 and Helm 3.3.2. As a possible workaround make sure to install plugins using a secure connection protocol like SSL.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
helm.sh/helm/v3Go | >= 3.0.0, < 3.3.2 | 3.3.2 |
helm.sh/helmGo | >= 2.0.0, < 2.16.11 | 2.16.11 |
Affected products
1Patches
6d9ef5ce8bad5Merge pull request from GHSA-c52f-pq47-2r9j
9 files changed · +31 −8
pkg/plugin/installer/local_installer_test.go+1 −1 modified@@ -37,7 +37,7 @@ func TestLocalInstaller(t *testing.T) { t.Fatal(err) } - source := "../testdata/plugdir/echo" + source := "../testdata/plugdir/good/echo" i, err := NewForSource(source, "") if err != nil { t.Fatalf("unexpected error: %s", err)
pkg/plugin/installer/vcs_installer_test.go+1 −1 modified@@ -56,7 +56,7 @@ func TestVCSInstaller(t *testing.T) { } source := "https://github.com/adamreese/helm-env" - testRepoPath, _ := filepath.Abs("../testdata/plugdir/echo") + testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo") repo := &testRepo{ local: testRepoPath, tags: []string{"0.1.0", "0.1.1"},
pkg/plugin/plugin.go+7 −1 modified@@ -96,6 +96,12 @@ type Metadata struct { // Downloaders field is used if the plugin supply downloader mechanism // for special protocols. Downloaders []Downloaders `json:"downloaders"` + + // UseTunnelDeprecated indicates that this command needs a tunnel. + // Setting this will cause a number of side effects, such as the + // automatic setting of HELM_HOST. + // DEPRECATED and unused, but retained for backwards compatibility with Helm 2 plugins. Remove in Helm 4 + UseTunnelDeprecated bool `json:"useTunnel,omitempty"` } // Plugin represents a plugin. @@ -200,7 +206,7 @@ func LoadDir(dirname string) (*Plugin, error) { } plug := &Plugin{Dir: dirname} - if err := yaml.Unmarshal(data, &plug.Metadata); err != nil { + if err := yaml.UnmarshalStrict(data, &plug.Metadata); err != nil { return nil, errors.Wrapf(err, "failed to load plugin at %q", pluginfile) } return plug, validatePluginData(plug, pluginfile)
pkg/plugin/plugin_test.go+11 −4 modified@@ -178,7 +178,7 @@ func TestNoMatchPrepareCommand(t *testing.T) { } func TestLoadDir(t *testing.T) { - dirname := "testdata/plugdir/hello" + dirname := "testdata/plugdir/good/hello" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -205,8 +205,15 @@ func TestLoadDir(t *testing.T) { } } +func TestLoadDirDuplicateEntries(t *testing.T) { + dirname := "testdata/plugdir/bad/duplicate-entries" + if _, err := LoadDir(dirname); err == nil { + t.Errorf("successfully loaded plugin with duplicate entries when it should've failed") + } +} + func TestDownloader(t *testing.T) { - dirname := "testdata/plugdir/downloader" + dirname := "testdata/plugdir/good/downloader" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -244,7 +251,7 @@ func TestLoadAll(t *testing.T) { t.Fatalf("expected empty dir to have 0 plugins") } - basedir := "testdata/plugdir" + basedir := "testdata/plugdir/good" plugs, err := LoadAll(basedir) if err != nil { t.Fatalf("Could not load %q: %s", basedir, err) @@ -288,7 +295,7 @@ func TestFindPlugins(t *testing.T) { }, { name: "normal", - plugdirs: "./testdata/plugdir", + plugdirs: "./testdata/plugdir/good", expected: 3, }, }
pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml+11 −0 added@@ -0,0 +1,11 @@ +name: "duplicate-entries" +version: "0.1.0" +usage: "usage" +description: |- + description +command: "echo hello" +ignoreFlags: true +hooks: + install: "echo installing..." +hooks: + install: "echo installing something different"
pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml+0 −0 renamedpkg/plugin/testdata/plugdir/good/echo/plugin.yaml+0 −0 renamedpkg/plugin/testdata/plugdir/good/hello/hello.sh+0 −0 renamedpkg/plugin/testdata/plugdir/good/hello/plugin.yaml+0 −1 renamed@@ -5,6 +5,5 @@ description: |- description command: "$HELM_PLUGIN_SELF/hello.sh" ignoreFlags: true -install: "echo installing..." hooks: install: "echo installing..."
f2ede29480b5Merge pull request from GHSA-c52f-pq47-2r9j
9 files changed · +35 −8
pkg/plugin/installer/local_installer_test.go+1 −1 modified@@ -48,7 +48,7 @@ func TestLocalInstaller(t *testing.T) { t.Fatal(err) } - source := "../testdata/plugdir/echo" + source := "../testdata/plugdir/good/echo" i, err := NewForSource(source, "", home) if err != nil { t.Errorf("unexpected error: %s", err)
pkg/plugin/installer/vcs_installer_test.go+1 −1 modified@@ -61,7 +61,7 @@ func TestVCSInstaller(t *testing.T) { } source := "https://github.com/adamreese/helm-env" - testRepoPath, _ := filepath.Abs("../testdata/plugdir/echo") + testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo") repo := &testRepo{ local: testRepoPath, tags: []string{"0.1.0", "0.1.1"},
pkg/plugin/plugin.go+11 −2 modified@@ -22,9 +22,10 @@ import ( "path/filepath" "strings" - helm_env "k8s.io/helm/pkg/helm/environment" - "github.com/ghodss/yaml" + yaml2 "gopkg.in/yaml.v2" + + helm_env "k8s.io/helm/pkg/helm/environment" ) const pluginFileName = "plugin.yaml" @@ -120,12 +121,20 @@ func LoadDir(dirname string) (*Plugin, error) { } plug := &Plugin{Dir: dirname} + if err := validateMeta(data); err != nil { + return nil, err + } if err := yaml.Unmarshal(data, &plug.Metadata); err != nil { return nil, err } return plug, nil } +func validateMeta(data []byte) error { + // This is done ONLY for validation. We need to use ghodss/yaml for the actual parsing. + return yaml2.UnmarshalStrict(data, &Metadata{}) +} + // LoadAll loads all plugins found beneath the base directory. // // This scans only one directory level.
pkg/plugin/plugin_test.go+10 −3 modified@@ -64,7 +64,7 @@ func TestPrepareCommand(t *testing.T) { } func TestLoadDir(t *testing.T) { - dirname := "testdata/plugdir/hello" + dirname := "testdata/plugdir/good/hello" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -92,8 +92,15 @@ func TestLoadDir(t *testing.T) { } } +func TestLoadDirDuplicateEntries(t *testing.T) { + dirname := "testdata/plugdir/bad/duplicate-entries" + if _, err := LoadDir(dirname); err == nil { + t.Errorf("successfully loaded plugin with duplicate entries when it should've failed") + } +} + func TestDownloader(t *testing.T) { - dirname := "testdata/plugdir/downloader" + dirname := "testdata/plugdir/good/downloader" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -131,7 +138,7 @@ func TestLoadAll(t *testing.T) { t.Fatalf("expected empty dir to have 0 plugins") } - basedir := "testdata/plugdir" + basedir := "testdata/plugdir/good" plugs, err := LoadAll(basedir) if err != nil { t.Fatalf("Could not load %q: %s", basedir, err)
pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml+12 −0 added@@ -0,0 +1,12 @@ +name: "duplicate-entries" +version: "0.1.0" +usage: "usage" +description: |- + description +command: "echo hello" +useTunnel: true +ignoreFlags: true +hooks: + install: "echo installing..." +hooks: + install: "echo installing something different"
pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml+0 −0 renamedpkg/plugin/testdata/plugdir/good/echo/plugin.yaml+0 −0 renamedpkg/plugin/testdata/plugdir/good/hello/hello.sh+0 −0 renamedpkg/plugin/testdata/plugdir/good/hello/plugin.yaml+0 −1 renamed@@ -6,6 +6,5 @@ description: |- command: "$HELM_PLUGIN_SELF/hello.sh" useTunnel: true ignoreFlags: true -install: "echo installing..." hooks: install: "echo installing..."
b0296c0522e8validate plugin metadata before loading
9 files changed · +35 −8
pkg/plugin/installer/local_installer_test.go+1 −1 modified@@ -48,7 +48,7 @@ func TestLocalInstaller(t *testing.T) { t.Fatal(err) } - source := "../testdata/plugdir/echo" + source := "../testdata/plugdir/good/echo" i, err := NewForSource(source, "", home) if err != nil { t.Errorf("unexpected error: %s", err)
pkg/plugin/installer/vcs_installer_test.go+1 −1 modified@@ -61,7 +61,7 @@ func TestVCSInstaller(t *testing.T) { } source := "https://github.com/adamreese/helm-env" - testRepoPath, _ := filepath.Abs("../testdata/plugdir/echo") + testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo") repo := &testRepo{ local: testRepoPath, tags: []string{"0.1.0", "0.1.1"},
pkg/plugin/plugin.go+11 −2 modified@@ -21,9 +21,10 @@ import ( "path/filepath" "strings" - helm_env "k8s.io/helm/pkg/helm/environment" - "github.com/ghodss/yaml" + yaml2 "gopkg.in/yaml.v2" + + helm_env "k8s.io/helm/pkg/helm/environment" ) const pluginFileName = "plugin.yaml" @@ -119,12 +120,20 @@ func LoadDir(dirname string) (*Plugin, error) { } plug := &Plugin{Dir: dirname} + if err := validateMeta(data); err != nil { + return nil, err + } if err := yaml.Unmarshal(data, &plug.Metadata); err != nil { return nil, err } return plug, nil } +func validateMeta(data []byte) error { + // This is done ONLY for validation. We need to use ghodss/yaml for the actual parsing. + return yaml2.UnmarshalStrict(data, &Metadata{}) +} + // LoadAll loads all plugins found beneath the base directory. // // This scans only one directory level.
pkg/plugin/plugin_test.go+10 −3 modified@@ -64,7 +64,7 @@ func TestPrepareCommand(t *testing.T) { } func TestLoadDir(t *testing.T) { - dirname := "testdata/plugdir/hello" + dirname := "testdata/plugdir/good/hello" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -92,8 +92,15 @@ func TestLoadDir(t *testing.T) { } } +func TestLoadDirDuplicateEntries(t *testing.T) { + dirname := "testdata/plugdir/bad/duplicate-entries" + if _, err := LoadDir(dirname); err == nil { + t.Errorf("successfully loaded plugin with duplicate entries when it should've failed") + } +} + func TestDownloader(t *testing.T) { - dirname := "testdata/plugdir/downloader" + dirname := "testdata/plugdir/good/downloader" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -131,7 +138,7 @@ func TestLoadAll(t *testing.T) { t.Fatalf("expected empty dir to have 0 plugins") } - basedir := "testdata/plugdir" + basedir := "testdata/plugdir/good" plugs, err := LoadAll(basedir) if err != nil { t.Fatalf("Could not load %q: %s", basedir, err)
pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml+12 −0 added@@ -0,0 +1,12 @@ +name: "duplicate-entries" +version: "0.1.0" +usage: "usage" +description: |- + description +command: "echo hello" +useTunnel: true +ignoreFlags: true +hooks: + install: "echo installing..." +hooks: + install: "echo installing something different"
pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml+0 −0 renamedpkg/plugin/testdata/plugdir/good/echo/plugin.yaml+0 −0 renamedpkg/plugin/testdata/plugdir/good/hello/hello.sh+0 −0 renamedpkg/plugin/testdata/plugdir/good/hello/plugin.yaml+0 −1 renamed@@ -6,6 +6,5 @@ description: |- command: "$HELM_PLUGIN_SELF/hello.sh" useTunnel: true ignoreFlags: true -install: "echo installing..." hooks: install: "echo installing..."
c8d6b01d72c9validate plugin metadata before loading
9 files changed · +35 −8
pkg/plugin/installer/local_installer_test.go+1 −1 modified@@ -48,7 +48,7 @@ func TestLocalInstaller(t *testing.T) { t.Fatal(err) } - source := "../testdata/plugdir/echo" + source := "../testdata/plugdir/good/echo" i, err := NewForSource(source, "", home) if err != nil { t.Errorf("unexpected error: %s", err)
pkg/plugin/installer/vcs_installer_test.go+1 −1 modified@@ -61,7 +61,7 @@ func TestVCSInstaller(t *testing.T) { } source := "https://github.com/adamreese/helm-env" - testRepoPath, _ := filepath.Abs("../testdata/plugdir/echo") + testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo") repo := &testRepo{ local: testRepoPath, tags: []string{"0.1.0", "0.1.1"},
pkg/plugin/plugin.go+11 −2 modified@@ -22,9 +22,10 @@ import ( "path/filepath" "strings" - helm_env "k8s.io/helm/pkg/helm/environment" - "github.com/ghodss/yaml" + yaml2 "gopkg.in/yaml.v2" + + helm_env "k8s.io/helm/pkg/helm/environment" ) const pluginFileName = "plugin.yaml" @@ -120,12 +121,20 @@ func LoadDir(dirname string) (*Plugin, error) { } plug := &Plugin{Dir: dirname} + if err := validateMeta(data); err != nil { + return nil, err + } if err := yaml.Unmarshal(data, &plug.Metadata); err != nil { return nil, err } return plug, nil } +func validateMeta(data []byte) error { + // This is done ONLY for validation. We need to use ghodss/yaml for the actual parsing. + return yaml2.UnmarshalStrict(data, &Metadata{}) +} + // LoadAll loads all plugins found beneath the base directory. // // This scans only one directory level.
pkg/plugin/plugin_test.go+10 −3 modified@@ -64,7 +64,7 @@ func TestPrepareCommand(t *testing.T) { } func TestLoadDir(t *testing.T) { - dirname := "testdata/plugdir/hello" + dirname := "testdata/plugdir/good/hello" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -92,8 +92,15 @@ func TestLoadDir(t *testing.T) { } } +func TestLoadDirDuplicateEntries(t *testing.T) { + dirname := "testdata/plugdir/bad/duplicate-entries" + if _, err := LoadDir(dirname); err == nil { + t.Errorf("successfully loaded plugin with duplicate entries when it should've failed") + } +} + func TestDownloader(t *testing.T) { - dirname := "testdata/plugdir/downloader" + dirname := "testdata/plugdir/good/downloader" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -131,7 +138,7 @@ func TestLoadAll(t *testing.T) { t.Fatalf("expected empty dir to have 0 plugins") } - basedir := "testdata/plugdir" + basedir := "testdata/plugdir/good" plugs, err := LoadAll(basedir) if err != nil { t.Fatalf("Could not load %q: %s", basedir, err)
pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml+12 −0 added@@ -0,0 +1,12 @@ +name: "duplicate-entries" +version: "0.1.0" +usage: "usage" +description: |- + description +command: "echo hello" +useTunnel: true +ignoreFlags: true +hooks: + install: "echo installing..." +hooks: + install: "echo installing something different"
pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml+0 −0 renamedpkg/plugin/testdata/plugdir/good/echo/plugin.yaml+0 −0 renamedpkg/plugin/testdata/plugdir/good/hello/hello.sh+0 −0 renamedpkg/plugin/testdata/plugdir/good/hello/plugin.yaml+0 −1 renamed@@ -6,6 +6,5 @@ description: |- command: "$HELM_PLUGIN_SELF/hello.sh" useTunnel: true ignoreFlags: true -install: "echo installing..." hooks: install: "echo installing..."
ac7c07c37d87switched to stricter YAML parsing on plugin metadata files
9 files changed · +31 −8
pkg/plugin/installer/local_installer_test.go+1 −1 modified@@ -37,7 +37,7 @@ func TestLocalInstaller(t *testing.T) { t.Fatal(err) } - source := "../testdata/plugdir/echo" + source := "../testdata/plugdir/good/echo" i, err := NewForSource(source, "") if err != nil { t.Fatalf("unexpected error: %s", err)
pkg/plugin/installer/vcs_installer_test.go+1 −1 modified@@ -56,7 +56,7 @@ func TestVCSInstaller(t *testing.T) { } source := "https://github.com/adamreese/helm-env" - testRepoPath, _ := filepath.Abs("../testdata/plugdir/echo") + testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo") repo := &testRepo{ local: testRepoPath, tags: []string{"0.1.0", "0.1.1"},
pkg/plugin/plugin.go+7 −1 modified@@ -96,6 +96,12 @@ type Metadata struct { // Downloaders field is used if the plugin supply downloader mechanism // for special protocols. Downloaders []Downloaders `json:"downloaders"` + + // UseTunnelDeprecated indicates that this command needs a tunnel. + // Setting this will cause a number of side effects, such as the + // automatic setting of HELM_HOST. + // DEPRECATED and unused, but retained for backwards compatibility with Helm 2 plugins. Remove in Helm 4 + UseTunnelDeprecated bool `json:"useTunnel,omitempty"` } // Plugin represents a plugin. @@ -200,7 +206,7 @@ func LoadDir(dirname string) (*Plugin, error) { } plug := &Plugin{Dir: dirname} - if err := yaml.Unmarshal(data, &plug.Metadata); err != nil { + if err := yaml.UnmarshalStrict(data, &plug.Metadata); err != nil { return nil, errors.Wrapf(err, "failed to load plugin at %q", pluginfile) } return plug, validatePluginData(plug, pluginfile)
pkg/plugin/plugin_test.go+11 −4 modified@@ -178,7 +178,7 @@ func TestNoMatchPrepareCommand(t *testing.T) { } func TestLoadDir(t *testing.T) { - dirname := "testdata/plugdir/hello" + dirname := "testdata/plugdir/good/hello" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -205,8 +205,15 @@ func TestLoadDir(t *testing.T) { } } +func TestLoadDirDuplicateEntries(t *testing.T) { + dirname := "testdata/plugdir/bad/duplicate-entries" + if _, err := LoadDir(dirname); err == nil { + t.Errorf("successfully loaded plugin with duplicate entries when it should've failed") + } +} + func TestDownloader(t *testing.T) { - dirname := "testdata/plugdir/downloader" + dirname := "testdata/plugdir/good/downloader" plug, err := LoadDir(dirname) if err != nil { t.Fatalf("error loading Hello plugin: %s", err) @@ -244,7 +251,7 @@ func TestLoadAll(t *testing.T) { t.Fatalf("expected empty dir to have 0 plugins") } - basedir := "testdata/plugdir" + basedir := "testdata/plugdir/good" plugs, err := LoadAll(basedir) if err != nil { t.Fatalf("Could not load %q: %s", basedir, err) @@ -288,7 +295,7 @@ func TestFindPlugins(t *testing.T) { }, { name: "normal", - plugdirs: "./testdata/plugdir", + plugdirs: "./testdata/plugdir/good", expected: 3, }, }
pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml+11 −0 added@@ -0,0 +1,11 @@ +name: "duplicate-entries" +version: "0.1.0" +usage: "usage" +description: |- + description +command: "echo hello" +ignoreFlags: true +hooks: + install: "echo installing..." +hooks: + install: "echo installing something different"
pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml+0 −0 renamedpkg/plugin/testdata/plugdir/good/echo/plugin.yaml+0 −0 renamedpkg/plugin/testdata/plugdir/good/hello/hello.sh+0 −0 renamedpkg/plugin/testdata/plugdir/good/hello/plugin.yaml+0 −1 renamed@@ -5,6 +5,5 @@ description: |- description command: "$HELM_PLUGIN_SELF/hello.sh" ignoreFlags: true -install: "echo installing..." hooks: install: "echo installing..."
8 files changed · +109 −0
pkg/chartutil/requirements.go+13 −0 modified@@ -17,7 +17,9 @@ package chartutil import ( "errors" + "fmt" "log" + "regexp" "strings" "time" @@ -219,6 +221,9 @@ func ProcessRequirementsTags(reqs *Requirements, cvals Values) { } +// Validate alias names against this regexp +var aliasRegexp = regexp.MustCompile("^[a-zA-Z0-9-_]+$") + func getAliasDependency(charts []*chart.Chart, aliasChart *Dependency) *chart.Chart { var chartFound chart.Chart for _, existingChart := range charts { @@ -237,6 +242,11 @@ func getAliasDependency(charts []*chart.Chart, aliasChart *Dependency) *chart.Ch chartFound = *existingChart newMetadata := *existingChart.Metadata if aliasChart.Alias != "" { + // Make sure Alias is well-formed + if !aliasRegexp.MatchString(aliasChart.Alias) { + fmt.Printf("Invalid alias in dependency %q. Skipping.", aliasChart.Name) + continue + } newMetadata.Name = aliasChart.Alias } chartFound.Metadata = &newMetadata @@ -286,6 +296,9 @@ func doProcessRequirementsEnabled(c *chart.Chart, v *chart.Config, path string) chartDependencies = append(chartDependencies, chartDependency) } if req.Alias != "" { + if !aliasRegexp.MatchString(req.Alias) { + return fmt.Errorf("illegal alias name in %q", req.Name) + } req.Name = req.Alias } }
pkg/chartutil/requirements_test.go+21 −0 modified@@ -370,11 +370,19 @@ func TestGetAliasDependency(t *testing.T) { } // Failure case + resetName := req.Dependencies[0].Name req.Dependencies[0].Name = "something-else" if aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]); aliasChart != nil { t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name) } + // Add a bad alias name + req.Dependencies[0].Name = resetName + req.Dependencies[0].Alias = "$foobar" + if aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]); aliasChart != nil { + t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name) + } + req.Dependencies[0].Version = "something else which is not in the compatible range" if version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) { t.Fatalf("Dependency chart version which is not in the compatible range should cause a failure other than a success ") @@ -516,3 +524,16 @@ func TestDependentChartsWithSomeSubchartsSpecifiedInRequirements(t *testing.T) { } } + +func TestAliasRegexp(t *testing.T) { + for name, shouldPass := range map[string]bool{ + "abcdefghijklmnopqrstuvwxyzABCDEFG0987654321_-": true, + "$foo": false, + "bar$": false, + "foo\nbar": false, + } { + if aliasRegexp.MatchString(name) != shouldPass { + t.Errorf("name %q failed to pass its test", name) + } + } +}
pkg/plugin/plugin.go+10 −0 modified@@ -16,6 +16,7 @@ limitations under the License. package plugin // import "k8s.io/helm/pkg/plugin" import ( + "fmt" "io/ioutil" "os" "path/filepath" @@ -141,13 +142,22 @@ func LoadAll(basedir string) ([]*Plugin, error) { return plugins, nil } + loaded := map[string]bool{} for _, yaml := range matches { dir := filepath.Dir(yaml) p, err := LoadDir(dir) + pname := p.Metadata.Name if err != nil { return plugins, err } + + if _, ok := loaded[pname]; ok { + fmt.Fprintf(os.Stderr, "A plugin named %q already exists. Skipping.", pname) + continue + } + plugins = append(plugins, p) + loaded[pname] = true } return plugins, nil }
pkg/plugin/plugin_test.go+1 −0 modified@@ -137,6 +137,7 @@ func TestLoadAll(t *testing.T) { t.Fatalf("Could not load %q: %s", basedir, err) } + // This would fail if the duplicate plugin were loaded. if l := len(plugs); l != 3 { t.Fatalf("expected 3 plugins, found %d", l) }
pkg/plugin/testdata/plugdir/hello2/hello.sh+13 −0 added@@ -0,0 +1,13 @@ +#!/bin/bash + +echo "Hello from a Helm plugin" + +echo "PARAMS" +echo $* + +echo "ENVIRONMENT" +echo $TILLER_HOST +echo $HELM_HOME + +$HELM_BIN --host $TILLER_HOST ls --all +
pkg/plugin/testdata/plugdir/hello2/plugin.yaml+11 −0 added@@ -0,0 +1,11 @@ +name: "hello" +version: "0.1.0" +usage: "usage" +description: |- + description +command: "$HELM_PLUGIN_SELF/hello.sh" +useTunnel: true +ignoreFlags: true +install: "echo installing..." +hooks: + install: "echo installing..."
pkg/repo/index.go+24 −0 modified@@ -30,6 +30,7 @@ import ( "github.com/Masterminds/semver" "github.com/ghodss/yaml" + yaml2 "gopkg.in/yaml.v2" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/proto/hapi/chart" @@ -83,6 +84,14 @@ type IndexFile struct { PublicKeys []string `json:"publicKeys,omitempty"` } +// IndexValidation is used to validate the integrity of an index file +type IndexValidation struct { + APIVersion string `yaml:"apiVersion"` + Generated time.Time `yaml:"generated"` + Entries map[string]interface{} `yaml:"entries"` + PublicKeys []string `yaml:"publicKeys,omitempty"` +} + // NewIndexFile initializes an index. func NewIndexFile() *IndexFile { return &IndexFile{ @@ -283,9 +292,14 @@ func IndexDirectory(dir, baseURL string) (*IndexFile, error) { // This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails. func loadIndex(data []byte) (*IndexFile, error) { i := &IndexFile{} + if err := validateIndex(data); err != nil { + return i, err + } + if err := yaml.Unmarshal(data, i); err != nil { return i, err } + i.SortEntries() if i.APIVersion == "" { // When we leave Beta, we should remove legacy support and just @@ -296,6 +310,16 @@ func loadIndex(data []byte) (*IndexFile, error) { return i, nil } +// validateIndex validates that the index is well-formed. +func validateIndex(data []byte) error { + // This is done ONLY for validation. We need to use ghodss/yaml for the actual parsing. + validation := &IndexValidation{} + if err := yaml2.UnmarshalStrict(data, validation); err != nil { + return err + } + return nil +} + // unversionedEntry represents a deprecated pre-Alpha.5 format. // // This will be removed prior to v2.0.0
pkg/repo/index_test.go+16 −0 modified@@ -422,3 +422,19 @@ func TestIndexAdd(t *testing.T) { t.Errorf("Expected http://example.com/charts/deis-0.1.0.tgz, got %s", i.Entries["deis"][0].URLs[0]) } } + +const mockDuplicateIndex = ` +entries: + foo: {} + bar: {} + baz: {} + bar: {} +` + +func TestValidateIndex(t *testing.T) { + expect := `key "bar" already set in map` + err := validateIndex([]byte(mockDuplicateIndex)) + if strings.Contains(expect, err.Error()) { + t.Errorf("Unexpected error: %s", err) + } +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-c52f-pq47-2r9jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-15187ghsaADVISORY
- github.com/helm/helm/commit/6aab63765f99050b115f0aec3d6350c85e8da946ghsax_refsource_MISCWEB
- github.com/helm/helm/commit/ac7c07c37d87e09797f714fb57aa5e9cb99d9450ghsax_refsource_MISCWEB
- github.com/helm/helm/commit/b0296c0522e837d65f944beefa3fb64fd08ac304ghsax_refsource_MISCWEB
- github.com/helm/helm/commit/c8d6b01d72c9604e43ee70d0d78fadd54c2d8499ghsax_refsource_MISCWEB
- github.com/helm/helm/commit/d9ef5ce8bad512e325390c0011be1244b8380e4bghsax_refsource_MISCWEB
- github.com/helm/helm/commit/f2ede29480b507b7d8bb152dd8b6b86248b00658ghsax_refsource_MISCWEB
- github.com/helm/helm/security/advisories/GHSA-c52f-pq47-2r9jghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.