From de13145549d91222d119ed475672774ccfd7988b Mon Sep 17 00:00:00 2001 From: Luther Monson Date: Mon, 21 Sep 2026 22:10:47 -0700 Subject: [PATCH] test(ghrel): verify a real GitHub asset against the hash spc pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both ghrel production breaks were about what GitHub actually returns, and no hermetic test in this package could see either: they all assert against a fake upstream that ignores Accept and always returns bytes. A proxy that never sends the header is indistinguishable from one that does, against that server. That is why TestAPIAssetEndpointServesBytesNotJSON passed while the fleet served 1429 bytes of metadata JSON as zlib. This fetches the real asset through the proxy, twice, and checks the sha256 against the value spc pins — so it fails for the same reason spc failed, rather than for a reason I chose in advance. The second fetch covers the hit path: a cache that stores the right bytes and returns the wrong ones is equally broken. Behind a `livegithub` build tag so CI stays hermetic and offline: go test -tags livegithub ./pkg/proxies/ghrel/ -run TestLive -v Verified against api.github.com: 1502830 bytes, sha256=bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16 on both the cold fetch and the cache hit. --- pkg/proxies/ghrel/live_real_github_test.go | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 pkg/proxies/ghrel/live_real_github_test.go diff --git a/pkg/proxies/ghrel/live_real_github_test.go b/pkg/proxies/ghrel/live_real_github_test.go new file mode 100644 index 0000000..17d3ef7 --- /dev/null +++ b/pkg/proxies/ghrel/live_real_github_test.go @@ -0,0 +1,68 @@ +//go:build livegithub + +// Run with: go test -tags livegithub ./pkg/proxies/ghrel/ -run TestLive -v +// +// Talks to the REAL api.github.com. Kept behind a build tag so CI stays +// hermetic, but it is the only test that could have caught either of the two +// production breaks: both were about what GitHub actually returns, and every +// hermetic test in this package asserts against a fake that does not model the +// asset endpoint's content negotiation. +package ghrelproxy + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "io" + "net/http" + "testing" +) + +// The exact artifact that broke the fleet twice: spc's zlib 1.3.2, whose +// sha256 spc pins. If the proxy serves anything other than the tarball -- +// metadata JSON, an error document, a truncated body -- this fails the same +// way spc did. +const ( + zlibAssetPath = "/repos/madler/zlib/releases/assets/357391855" + zlibSHA256 = "bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16" +) + +func TestLiveGitHubAssetBytesMatchSPCHash(t *testing.T) { + p := startProxy(t, Config{}) + base := "http://" + p.Addr() + + fetch := func(label string) string { + resp, err := http.Get(base + zlibAssetPath) + if err != nil { + t.Fatalf("%s: %v", label, err) + } + defer func() { _ = resp.Body.Close() }() + if resp.StatusCode != http.StatusOK { + t.Fatalf("%s: status %d", label, resp.StatusCode) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("%s: read: %v", label, err) + } + // The break-2 signature: a 200 whose body is the asset's metadata. + var probe map[string]any + if json.Unmarshal(body, &probe) == nil { + if _, isMeta := probe["browser_download_url"]; isMeta || probe["url"] != nil { + t.Fatalf("%s: served %d bytes of METADATA JSON, not the asset", label, len(body)) + } + } + sum := sha256.Sum256(body) + t.Logf("%s: %d bytes, sha256=%s", label, len(body), hex.EncodeToString(sum[:])) + return hex.EncodeToString(sum[:]) + } + + // First fetch populates the cache; second must serve identical bytes from + // disk. A cache that stores the right thing and returns the wrong thing on + // the hit path is just as broken. + if got := fetch("cold"); got != zlibSHA256 { + t.Fatalf("cold fetch sha256 = %s, want %s (this is exactly what spc rejects)", got, zlibSHA256) + } + if got := fetch("warm (cache hit)"); got != zlibSHA256 { + t.Fatalf("warm fetch sha256 = %s, want %s", got, zlibSHA256) + } +}