Skip to content

release: v0.9.0 — Python backend hardening + AppImage compat fix#16

Merged
I4cTime merged 3 commits intomainfrom
develop
Apr 18, 2026
Merged

release: v0.9.0 — Python backend hardening + AppImage compat fix#16
I4cTime merged 3 commits intomainfrom
develop

Conversation

@I4cTime
Copy link
Copy Markdown
Owner

@I4cTime I4cTime commented Apr 18, 2026

Release v0.9.0

This is the cumulative develop -> main promotion for the v0.9.0 release.

Headline changes

  • AppImage native-extension compatibility (AppImage Module Missing #13 follow-up). _vendor_compat.py now mutates sys.path instead of attempting shutil.rmtree (which is a silent no-op on read-only squashfs). electron/main.ts no longer sets PYTHONNOUSERSITE="" (which CPython treats as truthy and used to disable user site-packages). Vendored native deps trimmed from 5 to 1 (pydantic_core) by switching uvicorn[standard] -> bare uvicorn.
  • Backend security hardening (CodeQL). Fixed 2 critical command-injection alerts (protontricks.py, display.py) and 1 high token-leak (api/_app.py --print-token). Added defensive validate_user_path() calls inside prefix.py and saves.py FS functions for defense in depth on the path-injection chain.
  • API auth + CORS. Per-launch bearer token auth on every endpoint except /health//docs. CORS narrowed to 127.0.0.1/localhost origins.
  • Atomic writes everywhere. All config / VDF / env / profile / MangoHud / Heroic writes now go through fsutil.atomic_write_text (tempfile + fsync + os.replace) so a crash mid-write can't truncate user data. VDF endpoints also refuse to write while Steam is running.
  • api.py refactor. The 1183-LOC monolith is now an api/ package with _state.py, _models.py, _helpers.py, _app.py, and 8 domain-specific routers under routes/.
  • GTK4 dead code removed. app.py, __main__.py, theme.css deleted along with the obsolete protonshift console script.
  • Tests + tooling. Added 96 pytest cases (was 0). Ruff + Pyright in standard mode, both clean.

What's in this PR

Verification

Release plan

After merge, tag v0.9.0 and publish a GitHub release; that triggers .github/workflows/build-release.yml and ships the AppImage / .deb artifacts.

Made with Cursor

I4cTime and others added 3 commits April 16, 2026 16:15
- Introduced a new Flatpak manifest for the ProtonShift application, defining runtime, SDK, and necessary permissions.
- Added build instructions for Python dependencies and the ProtonShift application itself.
- Included a new social card image asset for enhanced sharing capabilities.

This setup lays the groundwork for packaging ProtonShift as a Flatpak application.
* refactor(python): execute full backend review plan (P0+P1+P2+P3)

Implements every item from docs/python-review.md except a deferred follow-up
on bundling python-build-standalone. 70 new pytest cases, ruff and pyright
clean across src/ + tests/.

P0 — release blockers
- P0-1 AppImage native ext compatibility: rewrite _vendor_compat.py to mutate
  sys.path instead of shutil.rmtree-ing files (no-op on read-only squashfs).
  Drop uvicorn[standard] -> uvicorn (kills uvloop/httptools/watchfiles, 3 of
  4 vendored .so). Trim _NATIVE_PACKAGES to actual deps (pydantic_core only).
  Fix electron/main.ts: PYTHONNOUSERSITE = "" was truthy and disabled the
  user site-packages fallback we wanted; now `delete env.PYTHONNOUSERSITE`.
- P0-2 single version source: __version__ in __init__.py is the only place,
  api app + electron package.json read from it. Bump 0.8.11 -> 0.9.0.
- P0-3 declare pydantic in pyproject.toml + python-runtime-requirements.txt.

P1 — correctness / safety
- P1-1 path traversal: new paths.py with sanitize_filename, safe_join,
  validate_user_path (rejects anything outside HOME / mounts / tmp).
  Applied at every API boundary that accepts a caller-supplied path
  (open-path, saves/restore, prefix delete, mangohud per-game,
  profiles, fixes, heroic configs).
- P1-2 API auth + CORS: per-launch bearer token via PROTONSHIFT_API_TOKEN,
  enforced by FastAPI middleware (compare_digest). CORS narrowed to
  localhost:3000 + file:// renderer. /health exempt for readiness probe.
  Electron mints the token, attaches it to every api-fetch.
- P1-3 atomic writes everywhere + Steam-running guard: vdf_config,
  env_vars, mangohud, profiles_storage, fixes, heroic_config all use
  fsutil.atomic_write_text now. update_launch_options + update_compat_tool
  return 409 when Steam is running so we don't get clobbered.
- P1-4 fix get_power_profiles parsing bug (was overwriting parsed list with
  a hardcoded one).
- P1-5 controllers: replace fake SDL GUID with canonical 16-byte
  little-endian bus/vendor/product/version layout from libsdl2.
- P1-6 remove dead BUILTIN_PROTON constant; discovery already returns
  these dynamically.
- P1-7 gamescope: build_gamescope_argv returns list[str] for Popen,
  build_gamescope_cmd returns shlex.join'd shell-safe string.

P2 — maintainability
- P2-1 pytest suite: 70 tests covering fsutil, paths, env_vars, vdf_config,
  tool_check, gamescope, controllers, vendor_compat, and end-to-end FastAPI
  smoke (auth, CORS, Steam guard, path traversal rejection).
- P2-2 delete dead GTK app.py + __main__.py + theme.css; drop the
  protonshift console script.
- P2-3 logging_setup.py: structured logging, replace silent except: pass
  with logged warnings on narrow exception types.
- P2-4 split api.py (1183 LOC) into api/ package: _state, _models,
  _helpers, _app + routes/{health,games,system,saves,mangohud,heroic,
  profiles,utility}. Backwards-compat __getattr__ shim on the package
  init for the legacy _API_TOKEN / _config_path attribute names.
- P2-5/7 fsutil.py: dir_size, human_size, atomic_write_text/bytes —
  replaces 3 duplicated _dir_size implementations.
- P2-6 cache discover_games with a 5s TTL.
- P2-9 promote heroic._resolve_heroic_root to public resolve_heroic_root.
- P2-10 Electron picks the API port locally and passes --port; drops the
  fragile stdout regex parsing.

P3 — style nits
- Hoist subprocess to module-level imports, fix ruff B904 chains, ruff
  I001 import ordering, datetime.UTC for UP017, etc.

Files: 57 changed, +3669 / -2638. Net code is much smaller than it looks
(GTK app removed). Verification: ruff, pyright (strict), pytest all green.

Deferred to a separate PR: bundling python-build-standalone in the AppImage
to remove the residual system-pydantic dependency entirely.

Made-with: Cursor

* security: fix CodeQL alerts (2 critical command injection + 1 high token leak)

Addressed all alerts CodeQL flagged on PR #15:

Critical fixes:
- protontricks.py: whitelist app_id (digits) and verb (alphanumeric+._-)
  before assembling the subprocess argv. Even though Popen with list args
  doesn't shell-execute, validating at the boundary keeps the surface clean
  and gives CodeQL a recognised sanitizer.
- display.py:set_resolution: whitelist monitor name against actually-detected
  outputs from get_monitors(), and bound width/height/refresh to sane ranges.

High fixes:
- api/_app.py: remove the --print-token CLI flag. Standalone curl users
  should set PROTONSHIFT_API_TOKEN themselves; we never want credentials
  leaking into journald, IDE consoles, or Electron logs.
- prefix.py / saves.py: defensively call validate_user_path() inside
  delete_prefix, get_prefix_info, find_save_paths, restore_backup, and
  backup_saves so each function refuses paths outside the user-writable
  roots even when called directly. This also satisfies CodeQL's data-flow
  analysis on the path-injection chain that previously fired 17 times.
- saves.py: sanitize app_id everywhere it joins onto a path, not just
  in backup_saves.

New tests (26 cases across 2 files):
- tests/test_protontricks.py — app_id and verb whitelist coverage incl.
  shell metachars, traversal attempts, oversize values.
- tests/test_display.py — set_resolution rejects shell metachars,
  unknown monitors, oversize dimensions, silly refresh rates.

Verification: ruff clean, pyright 0 errors, pytest 96/96.
Made-with: Cursor
@I4cTime I4cTime merged commit 3fb8591 into main Apr 18, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant