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:
1 parent
6622ae21f3
commit
4f4aa74672
3 files changed
+256
-96
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user