Third review:
- a broadcast counts once per recipient block, welcomes don't count
against block caps, and presence, cursor chat and pings have their own
per-address budget, so an office behind one address works together
- when everyone's reading budget runs out, the biggest readers are cut
off until it fits (their bytes freed at once), no cliff
- block counters are forgotten once only the table holds them
- install.sh: root's downloads, clone, swap file and built binary in a
root-only directory; the build user builds its own copy; the binary is
copied out with the build user's rights
- recommended open-mode settings in install.sh's config and the docs
Second security review:
- a broadcast counts once against the queued-bytes budget; each block of
addresses gets at most an eighth; when the budget runs out the biggest
consumer is shed, not the receiver
- forced compactions at most once a minute per file
- plugin downloads check the per-address share before the global budget
and before reading
- install.sh never replaces a Caddyfile with other sites (one import line,
once; install-test.sh in packaging lint); the built binary runs only as
the server's account
- bytes being read count against per-block and global budgets, the
biggest readers dropped first
- block multipliers x2 (/56) and x4 (/48), connection caps at most a
sixteenth/eighth of all; global publish and download budgets eight times
a /48's share
- problems capped at 200; writes refused below max(1 GiB, two files) free;
the private-server file cap documented
Fixes from the security review of the public server:
- team libraries only in key-published files on open servers; unpacking
bounded by the file cap (zip bombs refused)
- byte-bounded send queues per connection and in all, a bound on bytes
waiting for the hub, per-address and global download budgets
- batches checked against the file, per-address and total caps as files
grow (refused with a Reject the app rolls back); the core's recent-batch
memory bounded in bytes; nothing confirmed when a log can't be written
- usage reports kept in memory; per-kind message caps checked while read
- layered IPv6 limits (/64, /56, /48), a global publish rate, connections
reserved for sessions, short idle timeout for one-shot requests
- revoked links and removed files cut library connections
- data directory 0700, server key 0600 from creation
- install.sh: SSH ports from sshd, ufw left off when other services
listen, unprivileged builds of the latest tag, settings checked before
use, own Caddy snippet; systemd memory and network caps; backups check
free space and say they hold the key
- linger reads raw bytes after an oversized frame (tungstenite asserted)
packaging/server/install.sh sets up Debian/Ubuntu behind Caddy: binary from
its release archive, the source tree it sits in, the latest release or a
clone (temporary swap under 2 GB of memory), user, dirs and config only when
missing, systemd unit, Caddy site, nightly backup timer, ufw for 22/80/443;
safe to re-run for upgrades. docs/server.md gains Running a public server
(limits, expiry, moderation, operator responsibility, no end-to-end
encryption, threat model) and an install.sh walk-through; protocol, MCP,
CLI and parity docs updated.
The desktop app joins wss:// sessions (rustls with webpki roots, certificates always checked, clear errors; FIGCRAFT_CA_FILE for private authorities), with one TLS session shared by the reader and writer threads. figcraft-server stops gracefully on SIGTERM/SIGINT/SIGHUP/Ctrl+C, ships in Linux x86_64/aarch64 release archives, and docs/server.md gains a Deploy on a Linux server guide with a hardened systemd unit, Caddyfile, nginx config and Dockerfile in packaging/server.