diff --git a/src/simutrans/network/network_cmd.cc b/src/simutrans/network/network_cmd.cc
index 8347f3d73..a188aa20b 100644
--- a/src/simutrans/network/network_cmd.cc
+++ b/src/simutrans/network/network_cmd.cc
@@ -8,6 +8,10 @@
 #include "network_packet.h"
 #include "network_socket_list.h"
 
+#ifndef NETTOOL
+#include "../dataobj/environment.h"
+#endif
+
 #include <stdlib.h>
 
 
@@ -115,6 +119,21 @@ extern address_list_t blacklist;
 void nwc_service_t::rdwr()
 {
 	network_command_t::rdwr();
+#ifndef NETTOOL
+	// nwc_service_t is the admin-tool <-> server channel: nettool
+	// drives the request side, the server drives the response side.
+	// An in-game multiplayer client never legitimately exchanges
+	// nwc_service_t. Bail before parsing the body so a peer can't
+	// drive socket_list_t::rdwr / address_list_t::rdwr through an
+	// unbounded allocation loop with a wire-supplied count:
+	//   - server side: closes the pre-auth crash, since execute()'s
+	//     admin-auth gate fires only after rdwr() returns;
+	//   - client side: closes the equivalent OOM that a malicious
+	//     server could push to a joined client.
+	if (!env_t::server) {
+		return;
+	}
+#endif
 	packet->rdwr_long(flag);
 	packet->rdwr_long(number);
 	switch(flag) {
@@ -127,25 +146,33 @@ void nwc_service_t::rdwr()
 			packet->rdwr_str(text);
 			break;
 
+		// Direction-asymmetric: the list rides on the server->admin
+		// response only; the admin->server request body is just the
+		// flag. Each side handles exactly one leg, so there's no
+		// untrusted-bytes-into-list-count path left.
 		case SRVC_GET_CLIENT_LIST:
+#ifdef NETTOOL
 			if (packet->is_loading()) {
 				socket_info = new vector_tpl<socket_info_t*>(10);
-				// read the list
 				socket_list_t::rdwr(packet, socket_info);
 			}
-			else {
-				// write the socket list
+#else
+			if (packet->is_saving()) {
 				socket_list_t::rdwr(packet);
 			}
+#endif
 			break;
 		case SRVC_GET_BLACK_LIST:
+#ifdef NETTOOL
 			if (packet->is_loading()) {
 				address_list = new address_list_t();
 				address_list->rdwr(packet);
 			}
-			else {
+#else
+			if (packet->is_saving()) {
 				blacklist.rdwr(packet);
 			}
+#endif
 			break;
 		default: ;
 	}
