package mcpsrv_test import ( "net/http" "net/http/httptest" "strings" "testing" "sourcecraft.dev/bigbes/sr-ht-spec/mcpsrv" ) // The SDK's own DNS-rebinding guard is disabled (it cannot tell nginx from an // attacker), so this replacement is the only thing protecting /mcp. These cases // are the reason the bypass is defensible: a hostile Host is still refused, and // the legitimate proxied Host that the SDK guard would have rejected is let // through. func TestHostGuard(t *testing.T) { const origin = "https://spec.srht.bigb.es" for _, tc := range []struct { name string host string want int }{ {"proxied real hostname", "spec.srht.bigb.es", http.StatusOK}, {"proxied with port", "spec.srht.bigb.es:443", http.StatusOK}, {"case-insensitive", "SPEC.SRHT.BIGB.ES", http.StatusOK}, {"loopback for dev", "127.0.0.1:5091", http.StatusOK}, {"localhost for dev", "localhost:5091", http.StatusOK}, {"rebinding attacker domain", "evil.example.com", http.StatusForbidden}, {"attacker subdomain of us", "spec.srht.bigb.es.evil.com", http.StatusForbidden}, {"another srht service", "git.srht.bigb.es", http.StatusForbidden}, } { t.Run(tc.name, func(t *testing.T) { r, s := newFixture() h, err := mcpsrv.Handler(mcpsrv.Backend{Docs: r, Index: s}, "test", origin) if err != nil { t.Fatalf("Handler: %v", err) } req := httptest.NewRequest(http.MethodPost, "/mcp", strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"initialize"}`)) req.Host = tc.host req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json, text/event-stream") w := httptest.NewRecorder() h.ServeHTTP(w, req) if tc.want == http.StatusForbidden && w.Code != http.StatusForbidden { t.Fatalf("Host %q got %d, want 403 — the guard did not block it", tc.host, w.Code) } if tc.want == http.StatusOK && w.Code == http.StatusForbidden { t.Fatalf("Host %q got 403 — the guard blocked a legitimate request", tc.host) } }) } } // An unusable origin must leave a loud trail rather than silently unguarding the // endpoint. It still serves (refusing to start over a config typo is worse), but // Handler logs a warning; this pins that it does not instead start refusing // every request, which would look like the guard "working". func TestHostGuardWithoutOriginStillServes(t *testing.T) { r, s := newFixture() h, err := mcpsrv.Handler(mcpsrv.Backend{Docs: r, Index: s}, "test", "") if err != nil { t.Fatalf("Handler: %v", err) } req := httptest.NewRequest(http.MethodPost, "/mcp", strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"initialize"}`)) req.Host = "anything.example.com" req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json, text/event-stream") w := httptest.NewRecorder() h.ServeHTTP(w, req) if w.Code == http.StatusForbidden { t.Fatalf("unconfigured origin should not 403 every request; got %d", w.Code) } }