Commit graph

11 commits

Author SHA1 Message Date
6379127e17 cg-core: make cg-shuffle optionally seeded and reproducible
cg-shuffle gains an optional SEED and a `cg-shuffle-seed' dynamic var.
With a seed the order is deterministic and identical on every platform,
so a deal can be replayed, shared between players, or pinned in a test;
the index comes from secure-hash (spec-defined output everywhere, 7 hex
digits stay a fixnum even on 32-bit Emacs), so no bignum/overflow
behaviour can vary it.  With no seed the shuffle is the byte-for-byte
old system-random path -- normal play is unchanged.

Test cgt-shuffle-seeded pins determinism, permutation, override and a
cross-platform golden prefix; fails on the prior one-arg cg-shuffle.
Suite 159/159.
2026-08-03 22:18:02 -05:00
Claude
1a59003b13 cg-net: check a message is well shaped before any code walks it
Finding 6 of the 2026-07-29 security review: messages became data
structures before anyone checked them.  read never executes code, but
it can be made to yield self-referential structure (which hangs
ordinary list walks, plist-get included), objects that impersonate
internal record types (#s syntax yields real hash tables and records),
and any number of fresh symbols.

cg-net--valid-p accepts what is recognised: a proper, even-length
plist whose :type is in the per-peer whitelist -- the host accepts
(hello move), a client accepts (welcome state full) -- built only from
conses, vectors, strings, numbers and symbols, with no shared or
circular structure, at most cg-net-max-nodes nodes.  The check runs in
cg-net--filter before the Finding 2 scrub, so nothing cyclic or opaque
reaches scrub, handlers, or game code.  The cycle-tolerance of the
scrub walker remains as a second belt.

Named leftovers (in PATCHES.md): symbols are interned by read before
validation can see them, bounded only by Finding 4 line cap; and shape
is not semantics -- a hostile host can still send absurd but
well-shaped state, which is the reserved trust-model question.

Test cgt-net-shape-gate fails against the previous code with the
receipt (equal (5 (1 2 1 2 . #2) #s(hash-table)) (5)) -- the cycle and
the hash table both reached cg-net-apply-move.  cgt-net-valid-p pins
the unit contract (void-function before the patch).
2026-08-03 20:43:22 -05:00
Claude
ade2f38ee3 cg-net: reap disconnected clients with a process sentinel
Finding 5 of the 2026-07-29 security review: nothing watched for a
connection ending, so players who left stayed on the client list
forever and the host kept trying to send to them.

cg-net--host-sentinel removes a closed connection from the list; it is
installed on every accepted connection.  This also tidies the drops
made by the Finding 4 bounds, which delete-process over-limit peers.

Seat numbers are still not reused after a departure -- deliberate, and
recorded in the sentinel docstring: a stale seat must not be inherited
by a stranger mid-game.  Named in the handback as remaining behavior.

Test cgt-net-reaps-disconnected fails against the previous code: after
the client disconnected the list still held it (= 0 1).
2026-08-03 20:43:22 -05:00
Claude
640aaca1ae cg-net: bound the line buffer and the connection count
Finding 4 of the 2026-07-29 security review: incoming data was
collected until a newline arrived with no limit on how much, so a
connection sending forever without a newline grew the buffer until
memory ran out; the client list could grow the same way.  No login was
needed (Finding 1).

Two bounds, both defcustoms: cg-net-max-line (64 KiB -- generous for a
card game) closes any connection whose pending newline-less data
exceeds it, and cg-net-max-connections (8) closes connections arriving
past the limit before they are seated.  Finding 1's loopback default
narrows who can reach the port; this bounds what anyone who does reach
it can consume -- a mitigation that depends on another setting staying
at its default is not a bound.

Tests cgt-net-line-cap and cgt-net-connection-cap fail against the
previous code: the 80 KiB flooder stayed connected, and a third
connection stayed live past a cap of two (= 2 3).
2026-08-03 20:43:22 -05:00
Claude
f8873fb8fa 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].
2026-08-03 20:43:22 -05:00
Claude
9d3ec08d3c cg-net: strip text properties from everything arriving over the wire
Finding 2 of the 2026-07-29 security review (CONFIRMED there): text
properties survive the prin1/read round trip the protocol is built on,
so a malicious host could send a client strings whose properties rebind
keys or carry expressions evaluated during redisplay.

New cg-net--scrub walks a decoded message and passes every string
through substring-no-properties; conses and vectors are copied, shared
and circular structure is tolerated.  It is applied inside
cg-net--filter -- the one decode point both the host and the client
read through -- so both directions are covered at the boundary rather
than at each use site.

Test cgt-net-strips-properties (loopback, both directions) fails
against the previous code with properties intact: value (keymap (keymap)).
2026-08-03 20:43:21 -05:00
7918daf7ef Release 1.0.91 (pretest2): Info manual + generated README
Bump z per the releasable-set convention.  Ship the Info manual
(make info; make info-emacs fallback).  Generate README.md from
README.org via Emacs batch ox-md, with a pre-commit hook and
board screenshots.
2026-07-01 14:32:32 -05:00
5da3144c0a Cut 1.0.90 pretest: 500 mouse UX, version bump, NEWS, docs
Full-SVG 500 made mouse-operable for newcomers: kitty Discard button and
five-card cap, on-table phase banner, ? Help/Rules overlay with the bid
legend, legal-play dimming, card-size slider, and a layout pass that
moves the Help and size controls into the log panel so nothing overlaps.
Bump all files to 1.0.90, add NEWS, a README testing quick-start, and
make the shared engine files checkdoc-clean.
2026-06-26 18:48:31 -05:00
b5410e1830 Add 16 games and known-games research; bump to 1.0.60
Console UNICODE games on the cg-core EIEIO engine, all ERT-tested (88/88):
- Tableau solitaire (cg-solitaire.el): Klondike, FreeCell, Spider, Yukon,
  Canfield, Forty Thieves, Scorpion.
- Pile solitaire (cg-patience.el): Golf, TriPeaks, Pyramid.
- Trick-taking (cg-trick.el): Hearts, Spades, Whist, Oh Hell.
- Shedding/climbing: Crazy Eights (cg-eights.el), President (cg-president.el).

Wire all into the card-game chooser, Makefile, and README; add
known-games.org research collation; bump every file to 1.0.60.
2026-06-25 01:58:24 -05:00
2345f7e1a6 Add live multiplayer 500 over cg-net (cg-bid-net.el)
Host-authoritative networked 500: host sits South, joiners take W/N/E,
open seats filled by AI. Per-seat rotated state hides other hands and the
kitty; clients reuse the single-player renderer/commands via :around advice,
so cg-bid.el/cg-bid-ui.el are untouched. Adds cg-bid-host/cg-bid-join, a
start-now lobby with auto-start at four players, and cg-bid-shuffle-partners.
cg-net.el gains cg-net-connect-functions. Verified: clean byte-compile,
checkdoc, 34/34 ERT (incl. 3 new net tests) and a two-process TCP game.
2026-06-23 23:34:48 -05:00
6593b49b74 Add cg-net.el: host-authoritative TCP transport for multiplayer 2026-06-23 22:24:18 -05:00