diff --git a/cmd/wfctl/plugin_checksum.go b/cmd/wfctl/plugin_checksum.go index f8b2bae2..360cb3fd 100644 --- a/cmd/wfctl/plugin_checksum.go +++ b/cmd/wfctl/plugin_checksum.go @@ -34,6 +34,13 @@ func parseChecksumsTxt(body string) (map[string]string, error) { if strings.HasPrefix(name, " ") { return nil, fmt.Errorf("malformed checksums.txt line: %q (expected exactly two spaces between hash and filename)", line) } + name = strings.TrimPrefix(name, "./") + if name == "" { + return nil, fmt.Errorf("malformed checksums.txt line: %q (empty filename after normalization)", line) + } + if existing, ok := result[name]; ok && existing != hash { + return nil, fmt.Errorf("conflicting checksums.txt entries for %q", name) + } result[name] = hash } return result, nil diff --git a/cmd/wfctl/plugin_checksum_test.go b/cmd/wfctl/plugin_checksum_test.go index be8fcf79..62e27f69 100644 --- a/cmd/wfctl/plugin_checksum_test.go +++ b/cmd/wfctl/plugin_checksum_test.go @@ -30,6 +30,39 @@ func TestParseChecksumsTxt_Valid(t *testing.T) { } } +func TestParseChecksumsTxt_NormalizesDotSlashPrefix(t *testing.T) { + body := "abc123def456 ./wfctl-darwin-arm64\n" + got, err := parseChecksumsTxt(body) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got["wfctl-darwin-arm64"] != "abc123def456" { + t.Fatalf("normalized checksum = %q, want abc123def456", got["wfctl-darwin-arm64"]) + } +} + +func TestParseChecksumsTxt_RejectsConflictingDotSlashAliases(t *testing.T) { + body := "abc123def456 wfctl-darwin-arm64\n" + + "789abcdef012 ./wfctl-darwin-arm64\n" + _, err := parseChecksumsTxt(body) + if err == nil { + t.Fatal("expected conflicting checksum aliases to fail") + } + if !strings.Contains(err.Error(), "conflicting") { + t.Fatalf("error = %v, want conflicting checksum error", err) + } +} + +func TestParseChecksumsTxt_RejectsEmptyDotSlashName(t *testing.T) { + _, err := parseChecksumsTxt("abc123def456 ./\n") + if err == nil { + t.Fatal("expected ./ checksum entry to fail") + } + if !strings.Contains(err.Error(), "empty filename") { + t.Fatalf("error = %v, want empty filename error", err) + } +} + func TestParseChecksumsTxt_SkipsBlankLines(t *testing.T) { body := "\nabc123 plugin.tar.gz\n\n" got, err := parseChecksumsTxt(body) diff --git a/cmd/wfctl/update_test.go b/cmd/wfctl/update_test.go index 32b1f32f..4d21f930 100644 --- a/cmd/wfctl/update_test.go +++ b/cmd/wfctl/update_test.go @@ -378,6 +378,23 @@ func TestVerifyAssetChecksum_Valid(t *testing.T) { } } +func TestVerifyAssetChecksum_AcceptsDotSlashAssetPrefix(t *testing.T) { + data := []byte("fake binary content") + h := sha256.Sum256(data) + hash := hex.EncodeToString(h[:]) + checksumsContent := fmt.Sprintf("%s ./wfctl-darwin-arm64\n", hash) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(checksumsContent)) + })) + defer srv.Close() + + checksumAsset := &githubAsset{Name: "checksums.txt", BrowserDownloadURL: srv.URL} + if err := verifyAssetChecksum(checksumAsset, "wfctl-darwin-arm64", data); err != nil { + t.Fatalf("verifyAssetChecksum: %v", err) + } +} + func TestVerifyAssetChecksum_Mismatch(t *testing.T) { data := []byte("fake binary content") checksumsContent := "deadbeef wfctl-linux-amd64\n"