From f8873fb8fa969f595d9090ab548d52eaec4bfc26 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Aug 2026 20:31:58 -0500 Subject: [PATCH] cg-net: listen on this machine only by default Finding 1 of the 2026-07-29 security review: hosting bound 0.0.0.0 -- every interface -- with no password and no encryption, so anyone who could reach the machine's port was handed a seat and the game state. New defcustom cg-net-host-address defaults to 127.0.0.1; the wide-open value remains available and its docstring says exactly what choosing it means. This converts "anyone who can reach you" into "someone you deliberately let in", and bounds Finding 4's unauthenticated memory-exhaustion route to local callers along the way. Test cgt-net-host-loopback-default checks both the default value and the actual bound address (process-contact :local). Against the previous code it fails with (void-variable cg-net-host-address); the old bind, captured before the patch: [0 0 0 0 PORT]. --- cg-net.el | 22 ++++++++++++++++++++-- test/card-games-tests.el | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/cg-net.el b/cg-net.el index fd7861d..4fcf79d 100644 --- a/cg-net.el +++ b/cg-net.el @@ -60,6 +60,23 @@ "Default TCP port used to host or join a game." :type 'integer :group 'cg-net) +(defcustom cg-net-host-address "127.0.0.1" + "Address the host's listening socket binds when hosting a game. +The default, \"127.0.0.1\", accepts connections only from this +machine; remote players reach it through a tunnel they were +deliberately given (for example ssh port forwarding), which also +encrypts the traffic in transit. + +Setting this to \"0.0.0.0\" listens on every network interface, which +means anyone able to reach this machine's port can take a seat: there +is no password and no encryption on the wire. That can be a +reasonable choice on a trusted LAN, but it is a choice -- make it +deliberately." + :type '(choice (const :tag "This machine only (recommended)" "127.0.0.1") + (const :tag "Every interface (anyone who can reach you)" "0.0.0.0") + (string :tag "A specific interface address")) + :group 'cg-net) + (defvar cg-net-state-functions nil "Abnormal hook run on a client after the game state is updated. Each function is called with the client's game object.") @@ -157,11 +174,12 @@ properties stripped (`cg-net--scrub') before HANDLER sees them." (and cg-net--host (process-live-p (cg-net-host-server cg-net--host)))) (defun cg-net-host-start (game &optional port) - "Begin hosting GAME on PORT (default `cg-net-port'). Return the server process." + "Begin hosting GAME on PORT (default `cg-net-port'). Return the server process. +The socket binds `cg-net-host-address' -- by default, this machine only." (let* ((port (or port cg-net-port)) (server (make-network-process :name "cg-host" :server t :service port - :host "0.0.0.0" :family 'ipv4 :coding 'utf-8 + :host cg-net-host-address :family 'ipv4 :coding 'utf-8 :log #'cg-net--host-accept))) (setq cg-net--host (cg-net--host-make :server server :game game)) server)) diff --git a/test/card-games-tests.el b/test/card-games-tests.el index 0579310..f77f312 100644 --- a/test/card-games-tests.el +++ b/test/card-games-tests.el @@ -104,6 +104,25 @@ host must reach the client bare." (cg-net-disconnect) (cg-net-host-stop))))) +(ert-deftest cgt-net-host-loopback-default () + "Hosting binds to this machine only unless deliberately widened. +Finding 1 of the 2026-07-29 review: the default of +`cg-net-host-address' is loopback, and the listening socket really +binds it -- wider exposure is a setting the user turns on, not a +silent default." + (should (equal "127.0.0.1" (default-value 'cg-net-host-address))) + (condition-case _ + (delete-process + (make-network-process :name "cgt-probe4" :server t :service 0 + :host "127.0.0.1" :family 'ipv4)) + (error (ert-skip "TCP not available"))) + (let* ((hgame (make-instance 'cgt-net-game :env (list :counter 0))) + (srv (cg-net-host-start hgame 0))) + (unwind-protect + (should (equal [127 0 0 1] + (substring (process-contact srv :local) 0 4))) + (cg-net-host-stop)))) + ;;;; Gaps (ert-deftest cgt-gaps-deal ()