package main import ( "fmt" "net/http" "strings" "testing" ) // validatePushEndpoint is the ingest guard: only plain https URLs with a host. func TestValidatePushEndpoint(t *testing.T) { bad := []string{ "", // empty "http://fcm.googleapis.com/x", // not https "http://169.254.169.254/latest", // http + cloud metadata "https://", // no host "ftp://example.com/x", // wrong scheme "file:///etc/passwd", // wrong scheme "http://[::1]/x", // not https (loopback) } for _, e := range bad { if err := validatePushEndpoint(e); err == nil { t.Errorf("validatePushEndpoint(%q) = nil, want error", e) } } good := []string{ "https://fcm.googleapis.com/fcm/send/abc123", "https://updates.push.services.mozilla.com/wpush/v2/xyz", "https://web.push.apple.com/QABC", } for _, e := range good { if err := validatePushEndpoint(e); err != nil { t.Errorf("validatePushEndpoint(%q) = %v, want nil", e, err) } } } // guardDialControl is the enforcement layer: it blocks the resolved IP at dial // time, so even a stored endpoint (and any redirect hop) can't reach the // server's own network. This is what actually stops the SSRF. func TestGuardDialControlBlocksNonPublic(t *testing.T) { saved := allowPrivatePreviewHosts allowPrivatePreviewHosts = false defer func() { allowPrivatePreviewHosts = saved }() blocked := []string{ "127.0.0.1:443", // loopback "169.254.169.254:443", // cloud metadata (link-local) "10.0.0.5:6379", // private (internal Redis, etc.) "192.168.1.1:443", // private "[::1]:443", // IPv6 loopback "100.64.0.1:443", // CGNAT "0.0.0.0:443", // unspecified } for _, a := range blocked { if err := guardDialControl("tcp", a, nil); err == nil { t.Errorf("guardDialControl(%q) = nil, want blocked", a) } } if err := guardDialControl("tcp", "8.8.8.8:443", nil); err != nil { t.Errorf("guardDialControl(public) = %v, want nil", err) } } // End to end through the handler: a valid participant token does NOT let a member // register an internal/non-https push endpoint (proves the validator is wired in). func TestSubscribePushRejectsBadEndpoint(t *testing.T) { ts, srv := newTestServer(t) tok := mintDeviceToken(t, srv.db, "push-test") post := func(endpoint string) int { body := fmt.Sprintf(`{"token":%q,"subscription":{"endpoint":%q,"keys":{"p256dh":"p","auth":"a"}}}`, tok, endpoint) resp, err := http.Post(ts.URL+"/api/push/subscribe", "application/json", strings.NewReader(body)) if err != nil { t.Fatalf("post: %v", err) } resp.Body.Close() return resp.StatusCode } if got := post("http://169.254.169.254/latest/meta-data/"); got != http.StatusBadRequest { t.Fatalf("http metadata endpoint with valid token: want 400, got %d", got) } if got := post("https://fcm.googleapis.com/fcm/send/abc123"); got != http.StatusOK { t.Fatalf("valid https endpoint: want 200, got %d", got) } }