Apple's notarization ticket service dropped the connection in two release runs in a row
(once while validating the stapled DMG, once while stapling), failing the macOS job after a
successful build, signing and notarization. Stapling and its validation now try up to five
times, 15 s apart.
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.
- Browser client (step 4): the web build joins sessions a desktop app hosts
through the browser's WebSocket API (web-sys, binary frames, the same
message encoding). Its connection logic is a target-independent state
machine (collab-net driver.rs): hello on open, changes and presence held
until the welcome, throttled presence, keep-alive pings, a silent host
counted as dropped, reconnect with backoff, final refusals. Tested natively
against a scripted socket and a real host over loopback. Hosting stays
desktop-only (LiveLink::can_host; collab.peers canHost). https pages are
told up front that they can only reach ws:// sessions on this computer.
- Shared join logic (join.rs: GuestConfig, outcomes, taking in host messages)
and transport limits (limits.rs) used by the native guest and the browser.
- Invite links: `figcraft figcraft://join?...` joins on start (a bad link
opens the Join dialog with it); file:// arguments are paths. Windows
registers a per-user figcraft:// opener (HKCU\Software\Classes\figcraft)
behind a Registry trait (tests use a fake): collab.registerLinks {on?} and a
Preferences checkbox, default on for installed builds, never for builds
from source. macOS Info.plist declares CFBundleURLTypes; the Linux desktop
entry declares x-scheme-handler/figcraft and passes %U.
- Join dialog: Escape closes it and no longer reaches the canvas (selection
kept); its field has the keyboard when it opens.
- Docs: collab-protocol (browser client, links, mixed content), mcp,
control-protocol, README, web hosting notes, collaboration wording in the
progress docs (numbers unchanged).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Release prep: bump to 0.3.0; add linux-riscv64 release job
- release.yml: new linux-riscv64 job (cross-compiled on ubuntu-24.04 against ports.ubuntu.com
riscv64 libraries, tar.gz only, CLI smoke-tested under qemu-riscv64), ported from GridCraft;
the release job needs it so SHA256SUMS covers its tarball.
- packaging/linux/package.sh: CROSS_ARCH / CROSS_TARGET / CROSS_COMPILE / EMULATOR support.
- Version 0.2.0 -> 0.3.0 (cargo xtask version set), Cargo.lock refreshed.
- docs/releasing.md, docs/hardware-parity.md: list the riscv64 tarball.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CI: tell actionlint about the org's github-ubuntu-16core runner label
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>