470e1195150cf049eade7a36a0b859acdb7c58a8
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0a38dfbb19 |
fix(peer): reject unsafe game IDs in state marker paths
Scanner finding #12 ("state marker path escape"). The per-game state helpers in `state_paths.rs` joined a raw game ID below `<state_dir>/games/`. The public `setup_done_path` was therefore usable with an absolute or parent-containing ID by an embedding caller, and the legacy migration discovered IDs from directory names in the user's games folder and joined them unconditionally. Every shipping caller today validates its ID or takes it from the catalog, so this was a footgun rather than an exploited hole, but the fix is small and removes the reliance on every future caller remembering the rule. Add `validate_game_state_id`, which rejects separators and NUL and then delegates to `lanspread_db::content_manifest::validate_portable_component` (the catalog's own rules: no `.`/`..`, no trailing dot or space, no control or Windows-reserved characters, no Windows device names). Reusing the catalog validator rather than a private copy guarantees that any ID the catalog can publish is accepted here and that the two cannot drift apart. `setup_done_path` now returns `eyre::Result<PathBuf>`; it is the only state path the embedding application calls with an ID that may originate from UI input. `launch_settings_applied_path` leaves the public API and becomes `pub(crate)`; the two public launch-settings entry points (`apply_launch_settings_once`, `mark_launch_settings_applied`) validate the ID before any filesystem work. `game_state_dir` carries a `debug_assert!` documenting the contract for internal callers without turning a bad ID into a release-build panic; the migration test suite exercises that assertion in debug builds. Behaviour changes: - Legacy migration logs a warning, counts a failure and leaves the legacy marker in place for a games-folder directory whose name is not a portable game ID, instead of creating state below it. A new test covers a trailing-dot directory name. - The Windows launcher ignores a run request whose ID `setup_done_path` rejects, with a warning, mirroring the existing invalid-ID early return. Tests cover catalog-valid IDs that must remain accepted (embedded dots, spaces, `console.txt`, `com10`, non-ASCII) and unsafe IDs that must be rejected (empty, `.`, `..`, separators, NUL, trailing dot or space, device names, `a:b`). This ports the fallible API from the parallel security branch (lanspread2 commits 4146a0e and 9f26c63) onto the validator this branch already exports from `lanspread-db`. Test plan: - `cargo test -p lanspread-peer --lib`: 491 passed. - `just clippy`: clean. - On Windows, launch a game with a valid ID and confirm the setup marker is still written under `<app-data>/games/<id>/setup_done`. Claude-Session: https://claude.ai/code/session_01QRkCv4a4GqkajyamxmbSuA |
||
|
|
5c9f8bb321
|
fix(peer): fail instead of falling back to /tmp for the state directory
Security audit finding EXP2-SEC-06. When no explicit state directory, `LANSPREAD_STATE_DIR`, `HOME` or `USERPROFILE` was available, `resolve_state_dir` silently used `<temp_dir>/lanspread`. On a multi-user machine that is a predictable, world-writable location: another local user could pre-create it and then read or replace the Ed25519 peer identity and the download ownership journals stored there. Both shipping callers always provide a directory (the Tauri app passes its app-data path, the CLI its `--state-dir`), so the fallback was only reachable in unusual environments. Rather than derive a UID-specific temp path, peer startup now returns an error naming the accepted sources. This is the same fail-closed stance the codebase already takes for a malformed sharing policy. Test plan: `just test`. Manually, `LANSPREAD_STATE_DIR= HOME= lanspread-peer-cli ...` without `--state-dir` must refuse to start with a clear message; normal `just run` and `just peer-cli-run` are unaffected. Claude-Session: https://claude.ai/code/session_017C3Nbgwpdm3YNwZhhFLHwg |
||
|
|
60fd7ba0c2
|
feat(peer)!: cut over to authenticated catalog sharing
Replace address-only trust and pushed peer state with installation identities, SPKI-pinned QUIC, candidate-only discovery, and bounded responder-owned protocol-8 pulls. The runtime now owns each network generation and all admitted work through shutdown. Add exact bundled content identities, reproducible manifest publishing, capability-confined downloads, streaming BLAKE3 verification, quarantine and retry, and crash-recoverable download and install transactions. Ship generated fixture catalogs and fail closed when production manifests are absent. The Tauri backend exposes durable sharing policy, redacted identity state, and attempt-keyed transfer snapshots. Frontend consumption follows in the next commit. Repository-wide test certificates and protocol-7 paths are removed. BREAKING CHANGE: peers must use protocol 8 and exact catalog content artifacts; protocol-7 frames and shared-certificate identities are no longer accepted. Test Plan: - `just test` -- passed on the completed stack (708 workspace tests) - `just clippy` -- passed on the completed stack - `just build` -- passed with fixture catalogs on the completed stack - `just catalog-check-production` -- failed closed because the external production manifest corpus is absent - `git diff --cached --check` -- passed |
||
|
|
62cd9306bd
|
feat(peer): journal download file ownership
Track the exact regular files owned by each completed and in-flight peer download instead of sweeping every non-reserved path after cancellation. Bind the record to the canonical games directory, publish pending ownership before payload mutation, and use the final version.ini rename as the recovery commit point. Make replacement, cancellation, and startup recovery preserve unknown files and install state while removing stale or partial downloader-owned bytes. Add a new-format baseline so legacy discarded sentinels cannot make partially modified payloads ready, sync payload and journal state in transaction order, and serialize startup recovery against operation admission. Document ambiguous legacy target adoption, portable alias transitions, and the other ownership tradeoffs in the refactor decision log. Test Plan: - `just clippy` -- passed - `just test` -- passed (182 peer-core tests plus the full workspace) - `just fmt` -- Rust, TOML, and Prettier formatting completed; the command then stopped on 40 pre-existing rumdl findings in unrelated Markdown content - `git diff --cached --check` -- passed |
||
|
|
09709cc008
|
feat(peer): stamp launcher settings on first play, add PersonaName rewrite
Some games ship a SmartSteamEmu.ini somewhere under their installed
local/ tree with a `PersonaName = ...` line that must carry the player's
configured username. They also ship account_name.txt and language.txt
files that the launcher already overwrote with the username/language.
Previously that account_name.txt/language.txt overwrite happened inside
the install transaction, so it only applied to freshly (re)installed
games — games already installed by an older build never got fixed up,
and the SmartSteamEmu.ini PersonaName line was not handled at all.
This moves all per-user setting application out of install and into a
single one-shot step performed the first time a game is played, gated by
a new per-game marker `games/<id>/launch_settings_applied` under the
state dir. On first play we search the whole local/ tree and stamp:
- the username into the first account_name.txt,
- the language into the first language.txt,
- the username into the first SmartSteamEmu.ini PersonaName line,
preserving that line's existing line ending (\n or \r\n) and its
surrounding whitespace, leaving sibling lines untouched.
The marker only records that we *tried*: it is written unconditionally
after the first play, so a game with none of these files is still marked
done and never rescanned. Because already-installed games have no marker
yet, they are fixed up on their next play rather than only on reinstall.
To keep the marker honest across version changes, the install and update
transactions now clear it on success, so a freshly extracted local/ is
re-stamped on the next play.
Behavior changes from the user's perspective:
- The first time you press Play after this change, your username/
language are (re)applied to an existing install, including games you
installed before this feature existed.
- SmartSteamEmu.ini's PersonaName now reflects the launcher username.
Plumbing: account_name/language are removed from PeerCommand::InstallGame
/DownloadGameFiles[WithOptions] and the whole install handler chain, and
the Tauri pending_install_settings bookkeeping is gone — the launcher now
computes the values at play time in run_game and calls
lanspread_peer::apply_launch_settings_once. The headless harness gains a
`play` command exposing the same step for scripted testing.
Test Plan
- just test: new lanspread_peer::launch_settings unit tests cover the
PersonaName rewrite, \n/\r\n preservation, first-match search, the
unconditional marker, and the no-op-once-applied path; a transaction
test covers the install marker reset. Whole workspace is green.
- just clippy clean; the change adds no new clippy warnings (incl.
--tests).
- S38 (new in PEER_CLI_SCENARIOS.md): host run of lanspread-peer-cli
against the new fixture-persona/css RAR .eti (with --unrar) installs
css, then `play css` stamps the deeply-buried CRLF PersonaName line,
account_name.txt, and language.txt and creates the marker; a second
`play` is a no-op even after the values are reset externally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9835e77e8d
|
feat: store launcher state outside game dirs
Move launcher-owned metadata from game roots into the configured peer state area. Peer identity, the local library index, install intent logs, and setup markers now live under app/CLI state instead of being written beside games. The Tauri shell passes its app data directory into the peer, and the peer CLI runs the same path through its explicit --state-dir. Add a dedicated pre-start migration phase for legacy files. It migrates the old global library index, per-game install intents, and the old first-start marker into app state, then deletes legacy files only after the replacement write succeeds. Normal scan, install, recovery, and transfer paths no longer read legacy state files. Rename the old first-start meaning to setup_done and only set it after launching game_setup.cmd. Start/setup scripts keep the shared argument shape, while server_start.cmd now uses cmd /k and a visible window so server logs stay open for inspection. While validating the Docker scenario matrix, make download terminal events come from the handler after local state refresh and operation cleanup. This makes download-finished/download-failed safe points for immediate follow-up CLI commands. Also update the multi-peer chunking scenario to use a sparse archive large enough to actually span multiple production chunks. Test Plan: - just fmt - just test - just frontend-test - just build - just clippy - git diff --check - python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py Refs: local app-state migration discussion |