12 KiB
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
security-report/report.md that was deliberately not fixed, or only
partially fixed, and why. The judgement throughout is anchored in what lanspread
is: a desktop launcher for LAN parties where a room of people who know each
other share a fixed, operator-published catalog of games. Peers are
requester-anonymous by design, every byte a peer serves is verified against the
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.
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
NET-01 — Mutual TLS for inbound streams (partially fixed)
- Fixed: forged change hints. A hint is now honoured only when it arrives from the IP address at which the claimed peer was authenticated, which removes the reflected state-pull amplification the finding describes.
- Not fixed: requiring client certificates. Requester anonymity is intentional (documented in the threat model: "responder identity, not membership, is authenticated"). Anyone on the party LAN running the current build is supposed to be able to browse and download. mTLS would add certificate handling on every connection for no gain in that model, because a hostile participant can still mint a valid self-signed identity.
- The "information harvesting" sub-point (display names, game lists, Call to Play chat visible to any LAN client) is the product.
NET-06 — Unauthenticated chunk and Stream Install egress (informational)
By design; see NET-01.
EXP2-SEC-01 — Post-unpack manifest verification (partially fixed)
- Fixed: symlink/reparse-point audit before promotion, plus
unrar -ol-. - 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
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. 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
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: 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 System32cmd.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. Ordinary
S33 input is not necessarily catalog-identical, but it is selected local input
rather than peer-controlled bytes.
unrarruns 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
The log windows are opened from the frontend with the app's own URL, and the companion-window ownership logic is unit-tested TypeScript. The audit found no script-injection route, and the webview now has a CSP. Moving window creation into a Rust command to drop one permission is churn for a hypothetical.
SEC-FE-01 — ReDoS in the log-window regex filter
The regex is typed by the local user into their own log viewer. A user can
freeze their own UI thread with (a+)+$; nobody else can. Not a security issue
for this app.
Codex scan (security-report/report.md numbering)
[1] Anonymous LAN requesters can monopolize global pools (fixed 2026-09-12)
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. This is the behavior exercised by S33: the local user
replaces or adds
.etifiles and expects Ordinary Install to consume the current set. See EXP2-SEC-01.
[4] Sybil retry amplification (fixed)
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] Unbounded active-call rendering (fixed)
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] Aggregate state and UI publication (fixed)
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] Elevated mutable-script trust binding (fixed)
See SEC-IPC-01. Required elevation remains; mutable-path trust binding does not.
[11] Unbounded selected-root monitoring (fixed)
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 (fixed 2026-09-12)
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 [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. |