Skip to content

fix: secure runtime paths and launcher setup - #745

Closed
MohamedElGhandour wants to merge 804 commits into
metalsharp:mainfrom
MohamedElGhandour:codex/security-hardening
Closed

MohamedElGhandour wants to merge 804 commits into
metalsharp:mainfrom
MohamedElGhandour:codex/security-hardening

Conversation

@MohamedElGhandour

@MohamedElGhandour MohamedElGhandour commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Latest tested fixes: 5eacb277. The branch push succeeded, but GitHub currently shows “Processing updates” and still reports the old PR head; new CI has not started. The fork is now in a different repository network from metalsharp/MetalSharp, which may explain the stalled update. Local verification below covers the new commit.

In plain words

MetalSharp could load replacement runtime code without checking who supplied it, and some FEX paths were inserted into shell commands. Steam API keys could also be saved with permissions that exposed them to another local account. These fixes close those paths and preserve existing game prefixes, saves, and login data.

Summary

Harden runtime setup, launcher dependency installation, and credential storage in the C backend. The normal in-app updater already checks the publisher signature; these changes address separate setup and repair paths.

Changes

  • Execute FEX signature inspection directly, quote generated wrapper paths literally, and regenerate existing wrappers with facade version 4. Apostrophes and shell metacharacters stay part of the path.
  • Use packaged runtime archives for setup and Steam repair. Remove unverified downloads and loose/cache archive lookup; a missing packaged archive asks the user to reinstall the full official app.
  • Replace GOG's floating Git/Python installation with the fixed GOGDL v1.3.1 arm64 release, verified against an embedded SHA-256 before atomic installation. Existing users migrate through normal launcher actions without deleting their prefix.
  • Save Steam API keys atomically as owner-only files, tighten older files when read, and reject credential-file symlinks.
  • Restore TLS certificate verification when retrieving Sunshine checksum metadata. The local Sunshine API's self-signed-certificate handling remains scoped to localhost.

Why this approach

Regular app packaging already requires all six runtime archives. Removing the network fallback keeps those resources covered by the app's signature without adding another checksum publication mechanism. The FEX build already carries its runtime and skips those downloads.

The standalone GOG helper removes the Git, virtualenv, and unpinned package-install steps. Its existing command-line interface matches MetalSharp's callers. Failed verification leaves the previous helper and user data intact.

Verification

Check Result
make -C app/src-c test Passed, including new security regressions. Runtime-ready simulation skipped because this source checkout has no runtime archives.
make -C app/src-c VARIANT=fex BUILD_DIR="$PWD/app/src-c/build-fex" test Passed.
GOG bootstrap tests with the official pinned artifact, both variants Passed; verified installation and legacy-helper migration preserved prefix, token, and save fixtures. The downloaded executable was not run.
cargo fmt --manifest-path app-gpui/Cargo.toml --check Passed.
cargo test --manifest-path app-gpui/Cargo.toml --locked --offline 95 passed, 2 existing tests ignored.
python3 tools/ci/verify-dmg-workflow.py Passed; all six required bundle assets confirmed.
C formatting on changed blocks and new test files; git diff --check; shell syntax Passed. Existing unrelated formatting was preserved.
codesign --verify --deep --strict on both built backend binaries Passed; this does not validate a release DMG or publisher notarization.

Backend tests used isolated temporary homes. Regression cases cover crafted paths, invalid CDHashes, old-wrapper regeneration, missing or shadowed bundles, tampered helper downloads, initialized GOG upgrades, legacy credential permissions, and symlink target preservation.

Real-game launch smoke — 28 October 2026

Tested commit 753bb86 with Deadlock (1422450), D3DMetal, regular/Rosetta, using the existing Steam library on the internal drive. Both the frontend and backend were compiled from the PR; unchanged icons and runtime resources were copied from the installed 1.0.0 app into a separate, locally ad-hoc-signed test app. Full deep/strict signature verification passed. The installed application bundle was not modified.

Surface / action Expected Observed Verdict
Local app startup Supervise the PR backend Active backend process path and SHA-256 matched the assembled PR package. Pass
MetalSharp Play button Launch the installed Windows game through D3DMetal deadlock.exe started; backend reported appid 1422450 and the same game PID. D3DMetal was loaded. Pass
Deadlock window Render actual game content Two window captures showed different rendered 3D lobby frames and the HUD. Pass
Sustained launch Keep the game alive after startup The same game process remained running for 07:16 before deliberate cleanup. Pass
Cleanup Stop only the test game and app PID-scoped stop removed the test game; the local test app and its backend exited gracefully. Pass

This was a launch/rendering smoke test, not a multiplayer match, input/audio qualification, performance benchmark, or FEX game test. Existing Steam authentication was reused; no new store login was performed.

PR Readiness (MANDATORY)

  • Real-game launch test completed — Deadlock, D3DMetal, regular/Rosetta, internal Steam library; launch and rendering verified above.
  • No secrets or personal absolute /Users/... paths introduced.
  • Config/rules TOML validation: N/A; no rules or DLL maps changed.
  • Version metadata: N/A; no version bump.
  • Bottle/runtime migration and launch behavior preserved by regression tests; rollback noted below.
  • Architecture docs updated for bundle recovery and GOG installation.
  • Regression tests added for the affected security-sensitive behavior.

Follow-up verification — 4 November 2026

Merged current main while preserving the published branch history. Upstream rewrote earlier commits; the equivalent old/new 1.0 release trees were identical, so reconciliation used that tree as the common content base. All upstream data-location, Steam-launch and MetalFX changes are retained.

Upstream and this branch had independently assigned facade version 3 to different wrapper updates. The combined facade now uses version 4, with a regression verifying that version-3 wrappers regenerate.

Check Result
Regular and FEX C backend suites Passed, including security regressions and combined wrapper regeneration.
GPUI formatting and tests (cargo test --locked --offline) Passed: 103 tests; 3 ignored.
Backend route inventory, DMG workflow contract, shell syntax, diff whitespace Passed.
Both backend binaries: deep/strict code-sign verification Passed.

Runtime archive simulation and integration with a real MetalSharp Wine app were skipped because those assets are absent. The earlier Deadlock launch test remains evidence for the original security commit; no new game launch or release packaging is claimed for this merge.

Risk and remaining checks

A damaged installation now needs the full official app reinstalled instead of downloading replacement runtime archives. Future GOGDL upgrades must update the pinned asset and digest together. Reverting this commit restores the previous behavior.

FEX game launching, standard full-Xcode packaging, release DMG signing/notarization, and real store login remain untested. The separate local regular app was assembled and its full code signature verified for the launch test above. Wine's broad file access and compatibility-related browser sandbox flags need separate isolation/compatibility work; this PR does not claim to make untrusted Windows executables safe.

Created by Codex (GPT-6).

aaf2tbz added 30 commits October 4, 2026 10:29
Replace the connected-workbench modules with a live controller that drives
the real C backend on :9274 exactly as the Electron renderer did: setup
wizard (runtime/Steam installs into ~/.metalsharp, API key saves), library
sync for Steam and Ubisoft, Play/Stop with external-exit detection, hero
game settings, bottle route changes, launcher buttons, Settings actions,
updater download/install/restart, post-update migration, streaming, and the
GOG/Epic/GameJolt/emulator Sharp library.

The backend now removes the previous route's DLLs before staging a new
bottle route so switching e.g. DXMT -> D3DMetal leaves no stale DLLs.

Add package-app.sh to build MetalSharp.app with the com.metalsharp.app
identity expected by update.sh.
- First-launch intro video: MetalSharp-Startup.mp4 plays over the window
  with "Skip intro" (WKWebView overlay), remembered once seen.
- Cmd+P Process Manager HUD with helper sampling, MetalFX toggle/factor,
  non-Steam Wine force kill, and the --process-manager-overlay mode.
- LibraryView's one-time vkd3d default-rules repair.
- Sharp installer tooling reachable from SharpView: Launch Doctor and
  diagnostics drawer, D3DMetal launch path with track-running, bottle
  badges and launch log, app/GameJolt rename, cover position.
- GameJolt store embedded in the Sharp page with the draggable split,
  hidden under app overlays; toasts stay visible above it.
- Steam: the library hero button only starts Steam (it toggled, so it
  stopped a starting client). Skip redundant Wine registry imports, poll
  readiness every 100 ms, restart stale sessions instead of reporting a
  false success, stop via wineserver -k plus a kill sweep, show pending
  Starting/Stopping states, verify Wine is gone after setup's Steam
  install, and bring the Steam installer window to the front once.
- Settings: draw the confirm dialog and language menu without nesting a
  deferred layer inside the deferred overlay (GPUI aborted the app); same
  for Sharp's resolution menu. Uninstall stops Wine sessions and the
  backend, deletes data off the UI thread and shows the result dialog on
  the main thread.
- Shutdown: children spawned from GCD worker threads inherited a blocked
  signal mask, so the backend never received SIGTERM; clear the mask for
  spawned processes and in the backend's main(). Quit no longer blocks:
  SIGTERM plus a detached 3 s SIGKILL watchdog. Run the startup update
  check after the first library load, as Electron does.
- Browser: per-provider persistent WebKit stores keep Steam, GOG, Epic and
  TheGamesDB sign-ins; GOG disconnect and Epic logout clear theirs.
- Packaging: fail when the graphics bundle lacks the vkd3d-proton, DXVK or
  DXMT lanes; stop forcing MACOSX_DEPLOYMENT_TARGET for cargo, which made
  the macOS 27 linker emit proc-macro dylibs dyld rejects.
- Bottles: a saved "dxmt" route is honoured when saving, launching,
  checking readiness and diagnosing instead of falling back to the game's
  default route. Route DLLs are staged when an installed game is first
  detected (marker bottles/steam_<id>/.route-staged), not only on first
  launch or Bottle save.
- Executables: Skyrim SE launches SkyrimSE.exe, and a "<Name>Launcher.exe"
  next to "<Name>.exe" no longer wins on directory order.
- Steam: a cold start whose UI never initialized gets one automatic
  restart if htmlcache/"First Run" is not written within 10 s. The Steam
  client is launched through "MetalSharp Steam.app" (built by packaging
  from assets/metalsharp-steam.icon) so its first window carries the
  MetalSharp Steam icon instead of MetalSharp's; without the helper the
  launch is unchanged.
- Audio: FAudio's Windows build ships in metalsharp-assets
  (assets/faudio), unpacks to runtime/faudio and is installed into the
  Steam prefix with a SkyrimSE.exe-only native override so xWMA NPC
  dialogue plays. Older installs extract it from the bundle on demand.
  Third-party notices in LICENSES/FAUDIO-*.
- Updated the MetalSharp app icon.
Settings -> Graphics runtime logs now keeps each direct game launch's
Wine stderr (errors, DLL loads, exceptions) in
logs/<route>/<appid>/launch.stderr.log instead of discarding it; before,
only Rain World and EVE wrote launch logs.
The GPUI app in app-gpui/ is now the application. Remove the Electron main
process, Vue renderer, shared types, npm/Vite/TypeScript/Biome/Prettier
tooling, electron-builder hooks and the Vue/Electron source tests.
app/src-c, app/native, app/tools, app/bundles, app/updater and the two
backend source-contract tests stay.

Release: release.yml builds the GPUI app with package-app.sh, signs it
inside-out with hardened runtime and tools/dmg/entitlements.mac.plist
(Developer ID when signing is prepared, ad hoc otherwise), builds
MetalSharp-<version>-arm64.dmg with tools/dmg/package-gpui-dmg.sh and
reuses the notarize/verify scripts. The DMG name, bundle id and signing
team stay as update.sh's recovery path requires.

The app version comes from CMakeLists.txt; set-version.sh no longer needs
Node. The intro video moves to app-gpui/assets/intro. CI drops the Vue,
Electron and JS/Vue CodeQL jobs and runs the backend contract tests in the
native job; the pre-commit hook checks rustfmt instead of TypeScript.
Docs, install.sh, CODEOWNERS and third-party notices are updated.
GOG's sign-in now redirects to login.gog.com, and its Steam, Xbox, Google
and Discord buttons open a popup that posts the login back to the opener.
Allow *.gog.com for GOG sign-in, open window.open popups with the
WebKit-provided configuration, and check popup navigations and redirects
against the provider hosts. Epic's allowlist gains the hosts its
PlayStation, Nintendo, Steam, Xbox, Apple, Facebook and LEGO logins
redirect through, and captcha frames may load over HTTPS in both windows.
The column count ignored the workspace panel's 1px border, so a full row
overflowed by 2px and its last card wrapped, leaving an empty column.
… Sharp

- Sharp apps, GOG, Epic and GameJolt now stage the selected route's DLLs
  next to the game exe when the route is saved and before launch, using
  the Steam staging (previous route DLLs removed by byte comparison).
  GOG gains /sharp-library/gog/set-engine; installer exes are skipped.
- When a Sharp app exits, programs it installed (new uninstall entries,
  Start Menu/Desktop shortcuts, and for launchers new game folders beside
  them, in Program Files or on mapped drives) are added to the library
  with the same route. Installers run before this are scanned once.
- External volumes get drive letters (Y: down) in the Sharp prefix so
  launcher folder pickers can reach them.
- The Sharp card's Open Folder button becomes Add Asset: pick files from
  the unpacked MetalSharp assets and copy them beside the app's exe,
  keeping a .metalsharp-backup of any replaced file.
- gamejolt.c is clang-formatted so the pre-commit check passes.
The API key rows now end with clickable links that open the key pages in
the mini browser, reusing the setup screen's help opener.
Collection gains Installed / Uninstalled tabs. Uninstalled lists owned
Steam games that aren't installed and installs them with Valve's macOS
steamcmd (Windows depots), downloaded once to ~/.metalsharp/tools.

- Backend (steamcmd.c): stdin-driven sign-in with Steam Guard code and
  mobile-approval states (only the account name is saved), Steam library
  listing with free space, a sequential install queue with progress and
  cancel, staging into the chosen library then moving the game into
  steamapps/common with its appmanifest. steamcmd is stopped when the
  backend exits.
- UI: sign-in sheet, internal/external library picker, per-card progress
  and cancel, library refresh when an install finishes.
- Cards show compatibility badges from the repo's sources: Playable
  (GAMES-SUPPORTED.md), May Run (mtsp-rules.toml), or kernel anti-cheat.
AMID EVIL intermittently exits during startup when its shipping exe is
launched directly, but starts reliably through Steam with the same staged
route DLLs. Route DLLs are still staged; the launch is then handed to
steam://rungameid like Baldur's Gate 3.
Nothing imports the hook, so it was never loaded; DXMT staging, the DXMT
readiness check (which looked for it in lib/dxmt) and Launch Doctor's
expected files no longer include it. Copies already in game folders are
still removed by route cleanup.
metalsharp-runtime.tar.zst on the bundles release now ships an ntdll.so
built from the MetalSharp Wine 11.17 tree with DXMT-only GSBASE/TEB
switching (fixes Unity/Mono titles such as Rain World and Besiege), pipe2
gated to macOS 27+, the msync client matching wineserver (WINEMSYNC), and
the MetalSharp 1.0 version string.
The description and its link sat in a wrapping row, so the text stopped
wrapping and ran under the key inputs and status badges. Stack the link
under the wrapped description instead.
The release check and CI bundle downloads still used the pre-transfer
aaf2tbz/metalsharp path and only worked through GitHub's redirect. The
update check's error responses also hard-coded current_version 0.76.0,
which set-version.sh does not bump; use MS_BACKEND_VERSION.
set-version.sh no longer rewrites a hardcoded version in updater.c, whose
fallback responses now use MS_BACKEND_VERSION; it checks for that instead.
With the repository variable METALSHARP_RELEASE_RUNNER set to local, the
release jobs run on the runner labelled metalsharp-release instead of
macos-14. The build job deletes its temporary signing keychain afterwards,
since a self-hosted runner keeps the user's keychain search list.
Picks up the steamcmd and GOG set-engine routes added to the C backend.
Replace Electron desktop with native GPUI application
package-local-preview.sh bundles libmetalsharp_host_runtime.dylib from the
CMake build directory, which the GPUI Preview workflow never produced.
aaf2tbz and others added 24 commits October 28, 2026 00:40
… VKD3D-Proton and DXVK caches per lane, and games keep those caches in the lanes' folders
… with early access, the graphics lanes and Get Into It
… back from FEX leaves no MetalSharp Wine app behind
…tion, every prefix included, without the updater's marker
…s before clearing Metal's cache that every Mullion game shares
…er builds left in a game's folders, once, at its next launch
…unning migration, and an install lock keeps both from replacing the app at once
…s-fex.toml is the regular rules on MetalSharp Wine's lanes plus the games verified there
… and updates, and a changelog entry crediting Deadlock's fixes
…Wine and everything it carries are covered with their license texts
…eds METALSHARP_WINE_APP instead of a maintainer's path
…11 and D3D10 (welcome guide, changelog, architecture)
…o longer migrates at its second launch, and a Steam prefix MetalSharp Wine built reads as FEX
…nloaded regular DMG instead of recovering, and so reinstalling, FEX
…, swapped in safely and then consumed, and a failed restore leaves the build unrecorded to retry
…-recover for the regular build updating itself, the downloaded DMG with its variant otherwise
MetalSharp 1.0.0: the Arm64 FEX build on MetalSharp Wine
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.

4 participants