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:
2026-09-12 13:49:41 +02:00
parent 6622ae21f3
commit 4f4aa74672
3 changed files with 256 additions and 96 deletions
+98 -78
View File
@@ -1,4 +1,6 @@
# Security findings not fixed (Fable 5.1 pass, 2026-09-02)
# Retained security decisions and residual risks
Reassessed after the 2026-09-12 security pass.
This file records every finding from
`SECURITY_AUDIT_2026-08-28_GEMINI-3.7-HIGH_TEAMWORK.md` and
@@ -11,11 +13,11 @@ bundled catalog's BLAKE3 authority, and a hostile participant can be unplugged.
Findings that only make sense against an internet-facing, multi-tenant threat
model are noted as such.
Fixed findings are documented in the individual commits on this branch and are
not repeated here. After a cross-review of the three parallel fix branches,
NET-05, Codex [7] and Codex [12] were ported from the second branch and moved
from "not fixed" to "fixed" (see the commits touching `justfile`,
`state_paths.rs` and the bounded wire collections in `lanspread-proto`).
The reassessment found that several availability findings had been dismissed too
aggressively. They are now fixed with finite aggregate, per-origin, retry, and
UI publication budgets. Entries retained below require trusted catalog input, an
explicit local operation, or a same-machine writer; they are not remote LAN-peer
integrity bypasses under this product model.
## Gemini audit
@@ -40,59 +42,60 @@ By design; see NET-01.
### EXP2-SEC-01 — Post-unpack manifest verification (partially fixed)
- Fixed: symlink/reparse-point audit before promotion, plus `unrar -ol-`.
- Not fixed: hashing every extracted file against the catalog manifest. The
`.eti` archives are BLAKE3-verified at download time, so their extracted
output is only as trustworthy as the catalog operator, who is trusted by
definition. Stream Install already verifies extracted output because it
receives extracted bytes from a peer; the ordinary path extracts locally from
verified archives.
- Retained: hashing every ordinary extracted file against the catalog manifest.
Ordinary Install intentionally consumes every current regular root `.eti`;
acceptance scenario S33 replaces a downloaded archive and requires those new
local bytes to be installed. Stream Install remains exact: it verifies every
path, kind, size, and BLAKE3 digest before promotion.
### EXP2-SEC-02 — RAR bomb / disk exhaustion
Same reasoning: a decompression bomb would have to be published in the catalog.
Games are large by nature (the audit's own numbers are hundreds of gigabytes for
a party catalog), and a preflight would need a new dependency for free-space
queries plus a per-game expected size that the ordinary manifest does not carry.
Disk-full during extraction already fails the transaction and rolls staging
back.
Ordinary archives may be locally replaced or added for S33, so a bomb need not
come from the catalog. The retained boundary is an explicitly selected local
root plus a user-started install. Games are large by nature and the ordinary
manifest does not define expected expanded bytes. Disk-full or extractor failure
rolls the transaction back, but decompressed bytes, CPU time, and free space are
not preflighted.
### EXP2-SEC-05 — Pre-verification chunk writes
Uncommitted downloads are never published: `version.ini` is written only after
every chunk verifies, and the ownership journal plus recovery path handle a
crash mid-download. Buffering 4 MiB per in-flight chunk in memory to avoid
writing bytes that are about to be verified anyway would raise memory use for no
integrity gain.
crash mid-download. Ordinary chunks can be 128 MiB; buffering complete chunks in
memory to avoid confined temporary writes would materially raise memory use
without changing publication integrity.
### EXP2-SEC-07 — Ambient filesystem calls in `stream_install.rs`
Architectural parity request. The staging directory is created empty and owned
by the transaction, every path is a validated `CanonicalCatalogPath` resolved by
`validate_relative_path` (which now applies the full portable component rules
and rejects symlink escapes). Refactoring the receiver onto capability handles
is a large change with no concrete exploit behind it.
Remote paths, kinds, sizes, and digests are catalog-owned, and the completed
staging tree is audited before transactional promotion. The receiver still uses
ambient path calls inside its owned staging directory. Exploiting that gap
requires a concurrent same-machine writer able to mutate the selected root; it
is retained as defense-in-depth work rather than a remote peer bypass.
### SEC-IPC-01 — Elevated execution of game scripts (partially fixed)
- Fixed: `sanitize_username` now also strips `& | < > ^` so a batch script's
textual `%~4` expansion cannot chain commands.
- Not fixed: running the scripts elevated, and re-hashing them at launch time.
Elevated setup is the feature: the shared games need registry entries,
redistributables and firewall rules installed. The scripts ship inside catalog
archives that are BLAKE3-verified before installation, so "untrusted P2P batch
scripts" mischaracterises them. Re-verifying the file at launch would only
defend against a local actor who can already write to the user's own game
directory, i.e. who already runs code as that user.
- Fixed: launch-time trust binding. A fixed-role elevated worker reloads the
bundled authority, matches the embedded `ContentId`, verifies exact size and
BLAKE3 from a no-follow locked handle, resolves System32 `cmd.exe`, and keeps
path locks alive in the command process. Changed, unmanifested,
reparse-backed, markerless, and streamed-only scripts fail closed.
- Retained: all three scripts still run elevated. This is required for registry
entries, redistributables, and firewall rules. Native Windows lock-transfer
behavior is not proven in this Linux checkout, and a trusted script may still
invoke mutable secondary executables or configuration.
- The proposed `[A-Za-z0-9_-]` username allowlist was rejected because it would
mangle ordinary names (spaces, umlauts).
### SEC-IPC-04 — Sandboxing `unrar` (partially fixed)
- Fixed: `-ol-` and the post-extraction link audit.
- Not fixed: Landlock/AppContainer style OS sandboxing of the sidecar. The input
is a catalog-verified archive; a process sandbox for it is not proportionate
to a LAN launcher. Note that the `-sl-` flag the audit recommends does not
exist (`-sl<size>` is a size filter).
- Not fixed: Landlock/AppContainer style OS sandboxing of the sidecar. Ordinary
S33 input is not necessarily catalog-identical, but it is selected local input
rather than peer-controlled bytes. `unrar` runs with `-ol-`, bounded output
capture and cancellation cleanup, a staging link audit, and bounded remote
Stream Install provider admission. Note that the `-sl-` flag the audit
recommends does not exist (`-sl<size>` is a size filter).
### SEC-IPC-05 — `allow-create-webview-window` capability
@@ -109,63 +112,80 @@ for this app.
## Codex scan (`security-report/report.md` numbering)
### [1] Anonymous LAN requesters can monopolize global pools
### [1] Anonymous LAN requesters can monopolize global pools (fixed 2026-09-12)
Global pools (64 connections, 64 control streams, 48 bulk transfers) are the
intended fairness model for a room of peers; per-origin quotas conflict with the
anonymous requester design, and legitimate use is bursty (ten people pulling the
newest game from one seeder). Discovery now has a per-source-IP budget, which
covers the cheapest flood. A participant who saturates a seeder at a LAN party
is visible and can be dealt with in person.
Requester anonymity remains, but resource anonymity does not. One observed IP
may start 8 handshakes per 3 seconds and retain at most 8 connections, 8 of 16
control tasks, 8 of 48 bulk transfers, 1 of 2 Stream Install providers, and 8
MiB of the shared 32 MiB response buffer. Raw and Stream Install sends have
absolute deadlines, so incremental progress cannot renew those slots
indefinitely.
### [3] Ordinary install extracts uncatalogued root archives (partially fixed)
- Fixed: link audit before promotion.
- Not fixed: restricting extraction to the catalog's archive set and verifying
extracted output. The scenario requires the local user to place an extra
`.eti` in their own game directory. See EXP2-SEC-01.
extracted output. This is the behavior exercised by S33: the local user
replaces or adds `.eti` files and expects Ordinary Install to consume the
current set. See EXP2-SEC-01.
### [4] Sybil sources can multiply one chunk deadline into a multi-hour loop
### [4] Sybil retry amplification (fixed)
The retry loop tries each distinct peer identity once per chunk by design,
cancellation is always available, and a real party LAN has a handful of peers,
not 64 hostile identities. A global attempt budget would make large legitimate
downloads give up earlier under flaky Wi-Fi.
One failed chunk may try at most eight distinct peer IDs at distinct endpoint
IPs. All retries share one nonrenewable 20-minute window created after the
initial failure; each in-flight attempt retains the earlier ten-minute limit.
Stream Install separately permits four endpoint IPs under one catalog-sized
total deadline.
### [5] One peer can force continuous rendering of thousands of active calls
### [5] Unbounded active-call rendering (fixed)
The 4096-event author bound is a wire bound; a peer that publishes thousands of
simultaneous calls degrades the ticker for everyone but this is a prank that the
room can see and stop. Semantic caps on open calls per author would be a product
decision (how many calls may one person host?) rather than a security fix.
One remote author may retain at most 128 creator roots. Frontend projection is
limited to 128 nominations and the latest 256 messages, and messages are sorted
once rather than after every append.
### [8] Sybil peers can exhaust aggregate state and unbounded UI publication
### [8] Aggregate state and UI publication (fixed)
Worst case is 64 authenticated identities each holding a 4 MiB author slice,
i.e. a few hundred MiB retained by a desktop process, and only while a hostile
host keeps dozens of identities alive on the party LAN. Aggregate byte budgets
and bounded latest-value UI channels are a redesign of the state layer, not a
fix, and are not warranted by this deployment model.
Committed state now has eight identities per endpoint IP, 16,384 aggregate
library rows, and 16,384 aggregate remote Call-to-Play events. Rejected
revisions retain watermarks instead of being pulled repeatedly. Each heavy UI
view keeps one queued snapshot and one replaceable pending snapshot while small
lifecycle events remain lossless and FIFO.
### [10] Mutable game scripts are launched elevated without trust binding
### [10] Elevated mutable-script trust binding (fixed)
See SEC-IPC-01.
See SEC-IPC-01. Required elevation remains; mutable-path trust binding does not.
### [11] Selected game directories can trigger unbounded monitoring work
### [11] Unbounded selected-root monitoring (fixed)
The monitored directory is the one the user selected on their own machine.
Budgeting traversal of the user's own files against the user is out of scope.
Index reads, games, archive fingerprints, recursive entries, depth, bytes,
elapsed scan time, `version.ini`, launch-settings traversal, and INI reads now
have explicit per-file and aggregate ceilings. Monitor failures retain the
previous complete snapshot and back off exponentially per selected root.
### [13] Catalog preflight can read outside the package root
### [13] Catalog preflight can read outside the package root (fixed 2026-09-12)
Publisher tooling run by the catalog operator over their own package tree; the
disclosed bytes are a bounded, trimmed `version.ini` echoed in the operator's
own error message.
Full-selection preflight now rejects a linked or non-directory package root
before the bounded, non-link `version.ini` read, and mismatch diagnostics omit
package-controlled observed contents.
## Summary
| Status | Findings |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Fixed | NET-02, NET-03, NET-04, NET-05, EXP2-SEC-03, EXP2-SEC-04, EXP2-SEC-06, SEC-IPC-02, SEC-IPC-03, SEC-DB-01, Codex [2], [6], [7], [9], [12], [14], [15] |
| Partially fixed | NET-01, EXP2-SEC-01, SEC-IPC-01, SEC-IPC-04, Codex [3], [10] |
| Not fixed | NET-06, EXP2-SEC-02, EXP2-SEC-05, EXP2-SEC-07, SEC-IPC-05, SEC-FE-01, Codex [1], [4], [5], [8], [11], [13] |
| Status | Findings |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Fixed | NET-02, NET-03, NET-04, NET-05, EXP2-SEC-03, EXP2-SEC-04, EXP2-SEC-06, SEC-IPC-02, SEC-IPC-03, SEC-DB-01, Codex [1], [2], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15] |
| Partially fixed | NET-01, EXP2-SEC-01, SEC-IPC-01, SEC-IPC-04, Codex [3] |
| Accepted/retained | NET-06, EXP2-SEC-02, EXP2-SEC-05, EXP2-SEC-07, SEC-IPC-05, SEC-FE-01 |
The retained items have these practical boundaries:
| Retained risk | Boundary |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Anonymous membership and metadata/content access | Intended LAN behavior; responder pinning, exact catalog identity, and active per-origin quotas protect integrity and availability. |
| Ordinary archive and extracted-output authority | Explicit local install and S33 mutation; links are blocked, but the current archive set, expanded bytes, and decompression cost are local user authority. |
| Pre-verification temporary writes | Confined ownership journals and a final sentinel prevent publication; failed chunks can still consume bounded temporary disk space. |
| Ambient Stream Install staging calls | Remote paths, sizes, and digests are exact; a concurrent same-machine root mutation remains the escape precondition. |
| Elevated execution | Only exact catalog-authorized scripts launch, but those trusted scripts intentionally receive administrator authority and may invoke mutable dependencies. |
| External `unrar` | No OS sandbox; local mutated archives remain user-level extractor input. |
| Main-window companion creation | Used for fixed local log windows; CSP and no known renderer injection path limit reachability. |
| User regex CPU | The pattern is local input; remote-controlled log lines can only supply the haystack. |
| Distributed saturation | One IP cannot monopolize active pools; cooperating hosts on several IPs can still reach the finite global limits. |
+114
View File
@@ -0,0 +1,114 @@
# Security review — 2026-09-12
## Scope and threat boundary
This pass reviewed the full Lanspread workspace after the Fable 5.1 remediation.
The product is a desktop launcher for LAN parties, not an Internet-facing
multi-tenant service. Remote LAN peers and their network traffic are untrusted.
The bundled catalog is trusted authority. A user-selected local game root and
explicit local install actions remain user authority. The three Windows launch
scripts intentionally run elevated.
## Reassessment of previously ignored findings
The earlier file dismissed several finite-resource findings too broadly. This
pass fixed anonymous pool monopolization, retry multiplication, oversized remote
state, heavy UI publication, selected-root ingestion, elevated-script trust
binding, and catalog-preflight traversal. The remaining accepted decisions and
their exact limits are recorded in `SECURITY_FIXES_FABLE5.1_IGNORED.md`.
The main retained decisions are:
- anonymous LAN membership, metadata browsing, and catalog-content downloads;
- Ordinary Install consuming the current root `.eti` set, including the S33
local-mutation workflow;
- transactional pre-verification disk writes;
- no OS sandbox around `unrar`;
- local-user regex CPU cost and the fixed companion-window capability; and
- required elevation for catalog-authorized setup, game, and server scripts.
These are proportionate for the application only because remote paths and
content remain catalog-bound, local mutations require an explicit local action,
and remote work now has finite global and per-origin budgets.
## Findings fixed in this pass
| Finding | Final boundary |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| mDNS advertised-address quota bypass | Candidate quotas use the observed response IP. One source gets 8 active-or-cooling candidates. |
| Unbounded mDNS dependency state | Cache records are capped at 1,024 globally and 128 per source; timers are deduplicated and capped at 4,096. |
| Anonymous request pool monopolization | One IP gets 8 handshake attempts per 3 seconds, 8/64 connections, 8/16 control tasks, 8/48 bulk tasks, and 1/2 Stream Install providers. |
| Renewable bulk-send deadlines | Raw chunks use one absolute ten-minute window; Stream Install uses a catalog-sized absolute window plus ten-minute inactivity through FIN. |
| Ordinary Sybil retry multiplication | At most 8 distinct identities at distinct IPs share one 20-minute retry window after the initial failure. |
| Stream Install drip and retry multiplication | At most 4 endpoint IPs share `10 min + 2 x catalog bytes / 1 MiB/s`; only written nonempty file bytes refresh inactivity. |
| Eager control-response allocation | An 8 MiB endpoint-IP budget and shared 32 MiB connector budget are acquired after manual four-byte length validation and held through decode. |
| Aggregate remote state | At most 16,384 remote library rows and 16,384 remote Call-to-Play events; rejected revisions retain watermarks. |
| Peer identity amplification | At most 8 authenticated identities may reside at one endpoint IP, within the global 64-peer limit. |
| Unbounded full-view publication | Remote-library and Call-to-Play views keep one queued and one replaceable pending snapshot; lifecycle events remain FIFO. |
| Call-to-Play projection cost | One author retains 128 creator roots; the UI projects 128 nominations and 256 messages and sorts messages once. |
| Selected-root ingestion | Index, traversal, depth, byte, elapsed-time, bounded `version.ini`, and 64 MiB aggregate settings-read limits fail without publishing partial state. |
| Mutable elevated Windows scripts | A fixed-role elevated worker reloads embedded catalog authority, verifies the locked script, uses System32 `cmd.exe`, and transfers locks into the child. |
| Catalog preflight root traversal | Package roots must be regular non-link directories before a bounded leaf read; diagnostics omit observed package contents. |
| Developer recipe shell injection | Just positional arguments and exported values are passed as quoted shell data. |
| Unused frontend shell authority | The main-window `shell:allow-open` capability was removed. |
## Independent follow-up review
A fresh source-to-sink review found and closed six additional gaps: response
semaphore head-of-line blocking, half-open handshake fairness, an unbounded
public readiness-sentinel read, aggregate settings-file reads, repeated monitor
backoff reset after a root switch, and an over-strict ten-minute provider egress
deadline. The corrected provider deadline is ten minutes plus one catalog copy
at 1 MiB/s. The handshake limiter drops excess Initials because issuing a Retry
token would bypass s2n's next connection-attempt callback.
The reviewer read back the follow-up range and found no remaining concrete
source-level bypass or regression.
## Dependency scan
`just security-audit` refreshed RustSec and scanned 578 locked Rust packages,
then audited the Deno lockfile. It found no known vulnerabilities in either
graph. RustSec reported seven informational warnings:
- six unmaintained build/transitive crates (`proc-macro-error` and five `unic-*`
crates); and
- `glib 0.18.5` unsoundness limited to `VariantStrIter` methods.
The affected `glib` API has no call site in Lanspread or its locked dependants,
and the GTK 0.18 branch is constrained by the current Tauri Linux backend. The
warnings are dependency maintenance work rather than reachable findings.
## Residual limits
- Native Windows execution was unavailable. Linux-visible worker authority,
parsing, digest, role, and handoff tests pass, but the Windows lock-transfer
test has not run.
- The absent production scripts may invoke mutable secondary executables or
configuration as administrator. The production payload must be audited before
claiming transitive administrator-code provenance.
- Ordinary Install deliberately accepts locally changed or additional `.eti`
archives. Their expanded output and decompression cost are not catalog
authority; links are skipped and audited before promotion.
- Stream Install staging still uses ambient path calls. Exploitation requires a
concurrent same-machine writer able to mutate the selected root.
- Per-origin fairness cannot stop cooperating hosts or one host using several
usable IP addresses; finite global limits remain authoritative.
## Validation
- `just fmt`: passed.
- `just clippy`: passed with warnings denied.
- `just test`: passed, including 541 peer tests and all workspace targets.
- `just frontend-test`: 94 tests passed.
- `just build-fixture`: passed.
- `just peer-cli-build`: passed.
- `just peer-cli-tests`: all four catalog profiles passed; the container
scenarios could not start because this host denies Podman's `newuidmap`
capability, including outside the sandbox.
- `just mdns-vendor-test`: 106 tests passed with socket access.
- `just security-audit`: no known Rust or Deno vulnerabilities; seven accepted
RustSec informational warnings.
Physical-LAN multicast behavior and native Windows execution remain runtime
validation boundaries.
+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