Skip to content

Guard chartUrl extraction against a malformed helm index - #1062

Open
mightbeanshuu wants to merge 1 commit into
meshery:masterfrom
mightbeanshuu:fix-artifacthub-charturl-panic
Open

Guard chartUrl extraction against a malformed helm index#1062
mightbeanshuu wants to merge 1 commit into
meshery:masterfrom
mightbeanshuu:fix-artifacthub-charturl-panic

Conversation

@mightbeanshuu

Copy link
Copy Markdown

Description

UpdatePackageData extracted the chart URL from a remote helm repository index.yaml with unguarded type assertions and index accesses (details in #1061):

urls, ok := pkgEntry.([]interface{})[0].(map[interface{}]interface{})["urls"]
chartUrl, ok := urls.([]interface{})[0].(string)

The comma-ok only guarded the final map/type lookups; the .([]interface{}) assertions and the [0] indexes were unchecked. Since this index data is remote/externally controlled (utils.ReadRemoteFile), a malformed but valid-YAML index — a non-list chart entry, an empty version list, or an empty urls list — panicked the model generator with an index-out-of-range / type-assertion error.

This guards each assertion and index and returns the existing ErrGetChartUrl instead. No behavior change for well-formed indexes.

Testing

Adds a hermetic httptest-based test (TestUpdatePackageDataMalformedIndex) covering the malformed shapes (empty version list, non-list entry, empty urls, non-string url) and the well-formed happy path. go test ./generators/artifacthub/ passes; gofmt/go vet clean.

Fixes #1061


I noticed #856 and #845 are also open on this file, touching different parts of UpdatePackageData/GenerateComponents; this change is on the chartUrl-extraction hunk only and should rebase cleanly. Happy to coordinate ordering.

  • Signed off all commits (DCO).

UpdatePackageData extracted the chart URL from a remote helm repository
index.yaml with unguarded type assertions and index accesses:

  urls, ok := pkgEntry.([]interface{})[0].(map[interface{}]interface{})["urls"]
  chartUrl, ok := urls.([]interface{})[0].(string)

The comma-ok only covered the final map/type lookups; the .([]interface{})
assertions and the [0] indexes were unchecked. Because this data is parsed
from a remote index.yaml (utils.ReadRemoteFile), a malformed or unusual but
valid-YAML index — a non-list chart entry, an empty version list, or an
empty urls list — panicked the model generator with an index-out-of-range
or type-assertion error.

Guard each assertion and index and return the existing ErrGetChartUrl
instead. Adds a hermetic httptest-based test covering the malformed shapes
and the well-formed happy path.

Signed-off-by: Anshu <amananshu2004@gmail.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request improves the robustness of the helm index parsing in generators/artifacthub/package.go by adding proper type assertions and length checks to prevent panics on malformed data. It also adds corresponding unit tests to verify this behavior. The review feedback suggests making the error messages more specific and compliant with Go style guidelines, as well as optimizing the unit tests by reusing a single test server instance across test cases.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +127 to 146
entryVersions, ok := pkgEntry.([]interface{})
if !ok || len(entryVersions) == 0 {
return ErrGetChartUrl(fmt.Errorf("Cannot extract chartUrl from repository helm index"))
}
firstVersion, ok := entryVersions[0].(map[interface{}]interface{})
if !ok {
return ErrGetChartUrl(fmt.Errorf("Cannot extract chartUrl from repository helm index"))
}
urls, ok := firstVersion["urls"]
if urls == nil || !ok {
return ErrGetChartUrl(fmt.Errorf("Cannot extract chartUrl from repository helm index"))
}
chartUrl, ok := urls.([]interface{})[0].(string)
urlList, ok := urls.([]interface{})
if !ok || len(urlList) == 0 {
return ErrGetChartUrl(fmt.Errorf("Cannot extract chartUrl from repository helm index"))
}
chartUrl, ok := urlList[0].(string)
if !ok || chartUrl == "" {
return ErrGetChartUrl(fmt.Errorf("Cannot extract chartUrl from repository helm index"))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To improve maintainability and make debugging easier, consider providing more specific error messages for each validation failure. Currently, all five checks return the exact same error message ("Cannot extract chartUrl from repository helm index"), making it difficult to pinpoint which validation failed.

Additionally, following the standard Go style guide (Go Code Review Comments), error strings should not be capitalized and should not end with punctuation. "chartUrl" can also be formatted as "chart URL" for better readability.

Suggested change
entryVersions, ok := pkgEntry.([]interface{})
if !ok || len(entryVersions) == 0 {
return ErrGetChartUrl(fmt.Errorf("Cannot extract chartUrl from repository helm index"))
}
firstVersion, ok := entryVersions[0].(map[interface{}]interface{})
if !ok {
return ErrGetChartUrl(fmt.Errorf("Cannot extract chartUrl from repository helm index"))
}
urls, ok := firstVersion["urls"]
if urls == nil || !ok {
return ErrGetChartUrl(fmt.Errorf("Cannot extract chartUrl from repository helm index"))
}
chartUrl, ok := urls.([]interface{})[0].(string)
urlList, ok := urls.([]interface{})
if !ok || len(urlList) == 0 {
return ErrGetChartUrl(fmt.Errorf("Cannot extract chartUrl from repository helm index"))
}
chartUrl, ok := urlList[0].(string)
if !ok || chartUrl == "" {
return ErrGetChartUrl(fmt.Errorf("Cannot extract chartUrl from repository helm index"))
}
entryVersions, ok := pkgEntry.([]interface{})
if !ok || len(entryVersions) == 0 {
return ErrGetChartUrl(fmt.Errorf("cannot extract chart URL: empty or invalid version list"))
}
firstVersion, ok := entryVersions[0].(map[interface{}]interface{})
if !ok {
return ErrGetChartUrl(fmt.Errorf("cannot extract chart URL: first version entry is not a map"))
}
urls, ok := firstVersion["urls"]
if urls == nil || !ok {
return ErrGetChartUrl(fmt.Errorf("cannot extract chart URL: missing urls field"))
}
urlList, ok := urls.([]interface{})
if !ok || len(urlList) == 0 {
return ErrGetChartUrl(fmt.Errorf("cannot extract chart URL: empty or invalid urls list"))
}
chartUrl, ok := urlList[0].(string)
if !ok || chartUrl == "" {
return ErrGetChartUrl(fmt.Errorf("cannot extract chart URL: invalid or empty chart URL string"))
}
References
  1. According to the standard Go style guide (Go Code Review Comments), error strings should not be capitalized and should not end with punctuation. (link)

Comment on lines +83 to +87
serve := func(index string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, index)
}))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Spinning up and tearing down a new httptest.Server for every single test case in the loop is inefficient and can slow down test execution.

Instead, you can spin up a single test server outside the loop and dynamically update the response body for each test case.

Here is how you can refactor the test:

func TestUpdatePackageDataMalformedIndex(t *testing.T) {
	var currentIndex string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		_, _ = io.WriteString(w, currentIndex)
	}))
	defer srv.Close()

	tests := []struct {
		name      string
		index     string
		expectErr bool
	}{
		// ... (test cases remain the same)
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			currentIndex = tt.index
			pkg := AhPackage{Name: "mychart", RepoUrl: srv.URL}
			err := pkg.UpdatePackageData()
			// ... (assertions remain the same)
		})
	}
}

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.

UpdatePackageData panics on a malformed helm repository index (unguarded [0] index / type assertion)

1 participant