docs(security): record final review and retained risks

Reassess the Fable ignored-finding ledger against the LAN-party threat model, move the newly bounded findings to fixed, and state the remaining S33, anonymous-membership, unrar, local-race, and elevated-script decisions precisely.

Add the 2026-09-12 scan report with dependency results, independent follow-up review, validation evidence, and platform limits; update architecture limits to match the implementation.

Test Plan:
- just fmt
- just clippy
- just test
- just frontend-test
- just build-fixture
- just peer-cli-build
- just mdns-vendor-test
- just security-audit
- git diff --check
This commit is contained in:
ddidderr committed 2026-09-12 13:49:41 +02:00
1 parent 6622ae21f3
commit 4f4aa74672
3 files changed
+256 -96

No files matched your search

+44 -18
View File
@@ -60,9 +60,12 @@ When a peer is discovered:
emit UI membership events. Advertised addresses must be unicast with a
nonzero port; multicast, broadcast, and unspecified targets are dropped
before any handshake packet is sent. The mDNS ingress queue and active
candidate negotiations are each capped at 64, and one source IP may hold at
most 8 active-or-cooling candidates so a single host cannot fill every slot
by rotating claimed peer IDs and ports.
candidate negotiations are each capped at 64, and one observed response IP
may hold at most 8 active-or-cooling candidates. The advertised A/AAAA target
is never used as provenance, so one host cannot escape the budget by rotating
target addresses, peer IDs, or ports. The vendored mDNS daemon also caps
cached records at 1,024 globally and 128 per response IP, with at most 4,096
deduplicated timers.
2. Reserve a candidate negotiation lease before queueing or awaiting work, then
establish a TLS-pinned connection to that exact endpoint. Missing or non-v8
records are rejected, and neither an ephemeral QUIC source port nor a payload
@@ -103,14 +106,20 @@ When a peer is discovered:
work for five seconds, and runs at most eight pinned pulls and eight hint
sends concurrently. Child work is drained lexically on shutdown.
- The server admits at most 64 unauthenticated handshakes, 64 established
connection scopes, and 32 control streams per connection. Each control stream
accepts one frame followed by request EOF and sends at most one response.
Inbound request frames are capped at 64 KiB (the QUIC receive window per
server stream matches it), response frames at 8 MiB, and control I/O has
ten-second deadlines. Wire collections (library games, Call to Play author
events) are deserialized through a bounded visitor that materializes at most
one element past the semantic limit and discards the rest, so an oversize
frame cannot claim more memory than a valid one before validation rejects it.
connection scopes, and 32 control streams per connection. One observed IP may
start at most 8 handshakes during one 3-second handshake window, before TLS
and established-connection leases exist; the source ledger itself holds at
most 256 IPs. One observed IP may retain at most 8 connections, 8 of the 16
global control tasks, 8 of the 48 global bulk tasks, and 1 of the 2 Stream
Install providers. Each control stream accepts one frame followed by request
EOF and sends at most one response. Inbound request frames are capped at 64
KiB (the QUIC receive window per server stream matches it), response frames at
8 MiB, and control I/O has ten-second deadlines. Response buffering permits at
most 8 MiB per endpoint IP within one shared 32 MiB pool. Wire collections
(library games, Call to Play author events) are deserialized through a bounded
visitor that materializes at most one element past the semantic limit and
discards the rest, so an oversize frame cannot claim more memory than a valid
one before validation rejects it.
### Call to Play replication
@@ -215,6 +224,10 @@ pull.
- presence of `local/` as a directory
- If the fingerprint is unchanged, reuse the cached local summary.
- Only run a recursive scan for new or changed games.
- Snapshot and recursive work has explicit entry, game, depth, byte, file-size,
and elapsed-time ceilings. Failure retains the previous complete state. Poll
failures back off from one second to five minutes per attempted root; a root
switch resets that delay once.
## Local State and Recovery
@@ -231,6 +244,9 @@ Downloaded and installed are independent predicates:
- Install and update transactions unpack into staging, then overwrite the first
discovered game-provided `account_name.txt` and `language.txt` files under the
staged tree from launcher settings before promoting it to `local/`.
- Launch-settings discovery uses one bounded traversal, reads at most 1 MiB per
INI and 64 MiB in aggregate under the same ten-minute clock, and finishes all
candidate reads before its first write or marker publication.
Reserved per-game paths:
@@ -325,8 +341,10 @@ Most scans become O(number of game dirs), with full recursion only when needed.
open/request/receive/check lifecycle of each ordinary chunk.
- An integrity failure quarantines `(PeerId, content_id)` in shared memory for
the rest of that peer runtime, independent of address changes. Transport
failures remain retryable without quarantine. Each failed chunk may try every
distinct eligible peer identity once; there is no separate numeric retry cap.
failures remain retryable without quarantine. Each failed chunk may try at
most eight distinct authenticated identities at distinct endpoint IPs. All
retries share one nonrenewable 20-minute window created after the initial
failure; an individual attempt still has the earlier ten-minute limit.
- A no-transfer local shortcut is allowed only when settled ownership records
the exact expected committed content ID. A matching `version.ini` alone, or a
legacy/pre-content-ID record, is not catalog-content proof.
@@ -343,9 +361,10 @@ Most scans become O(number of game dirs), with full recursion only when needed.
### Ordinary install extraction
- `install`/`update` extract every direct regular root `.eti` archive with the
external `unrar` unpacker into the staging directory. The archives were
BLAKE3-verified against the catalog when downloaded.
- `install`/`update` extract every current direct regular root `.eti` archive
with the external `unrar` unpacker into the staging directory. Downloaded
archives were BLAKE3-verified, but the ordinary path deliberately accepts
later local replacements and extra archives for the S33 workflow.
- `unrar` runs with `-ol-`, so symbolic-link entries are skipped, and the
staging tree is walked without following links before promotion. Any symlink
(or reparse point on Windows) aborts the transaction and rolls staging back,
@@ -359,6 +378,9 @@ Most scans become O(number of game dirs), with full recursion only when needed.
output and only when its direct regular root `.eti` set exactly equals the
catalog archive set. Missing or extra archives stop before the extraction
provider receives authority.
- Provider egress has one nonrenewable deadline of ten minutes plus the exact
catalog streamed bytes at 1 MiB/s, so large valid games are finite without a
fixed ten-minute whole-transfer cutoff.
- The Stream Install request names the exact catalog `ContentId`. All
path-bearing frames, including archive names, use `CanonicalCatalogPath` and
the fallible checked decoder distinguishes malformed framing from an explicit
@@ -371,8 +393,12 @@ Most scans become O(number of game dirs), with full recursion only when needed.
early corruption signal, not an authority boundary.
- Integrity failures quarantine the source for that catalog content and retry a
fresh transaction from another eligible peer. Transport failures retry without
quarantine; local I/O and cancellation stop. Every failed attempt rolls back
before the next source begins.
quarantine; local I/O and cancellation stop. At most four distinct endpoint
IPs are attempted. Every attempt shares a catalog-sized total deadline of ten
minutes plus two copies of the expected streamed bytes at 1 MiB/s. The
ten-minute inactivity timer refreshes only after a nonempty, catalog-checked
file chunk is written. Every failed attempt rolls back before the next source
begins.
- After complete catalog verification, account/language/persona settings are
rewritten in staging before promotion. The one-shot launch-settings marker is
written only after successful promotion; if it cannot be written, first play