Skip to content

fix: store GitHub API token encrypted via SecretStore (AES-256-GCM) - #116

Open
mock1ngbb wants to merge 1 commit into
SirDiabo:mainfrom
mock1ngbb:secret-store-patch
Open

fix: store GitHub API token encrypted via SecretStore (AES-256-GCM)#116
mock1ngbb wants to merge 1 commit into
SirDiabo:mainfrom
mock1ngbb:secret-store-patch

Conversation

@mock1ngbb

@mock1ngbb mock1ngbb commented Jul 15, 2026

Copy link
Copy Markdown

The Problem

The GitHub API token (personal access token) is stored as plaintext in settings.json alongside the app binary. This means:

  • Any process running on the same machine can read the token from disk
  • Anyone with file system access to <app>/settings.json has full use of the token
  • Users who share screenshots of their settings panel inadvertently expose the token (no password masking)
  • The token is serialized with the rest of the application settings, so a settings export/share leaks credentials too

The GitHub API token is used to authenticate requests to the GitHub API. With a plaintext token on disk, the credential boundary is reduced to "can this process read a JSON file?" — which is nearly everything on a desktop OS.

The Fix

1. Encrypted token storage (Services/SecretStore.cs)

  • AES-256-GCM encryption (NIST SP 800-38D) with a random 32-byte key
  • Ciphertext + key stored at ~/.local/share/GithubLauncher/ outside the app directory
  • Unix file permissions set to 0600 (owner read/write only)
  • Key auto-generated on first use — if the key file is deleted, a new one is created and the token must be re-entered
  • Token is cached in memory for the lifetime of the process — no repeated decryption overhead

2. JsonIgnore on AppSettings (Services/AppSettings.cs)

  • GitHubApiToken gains [System.Text.Json.Serialization.JsonIgnore] — it is never serialized to settings.json
  • Property getter/setter redirect through SecretStore.ReadToken() / SecretStore.WriteToken()
  • All existing code paths (settings panel, CLI, game manager) continue to work through the property redirect

3. Password masking (MainWindow.axaml.cs)

  • The token input field now uses PasswordChar = '*' — the token is masked in the UI
  • Token is still editable: typing into the field sets the new value; the clear button still works
  • When the settings panel loads, the token is populated from SecretStore (decrypted in memory)

4. Direct SecretStore access (Models/GameInfo.cs)

  • GetGitHubApiToken() reads directly from SecretStore.ReadToken() instead of re-loading AppSettings.Load() every call

Backward Compatibility

  • On first launch after this change, any existing token in settings.json will be ignored (the [JsonIgnore] property won't read from JSON)
  • Users will need to re-enter their token in the settings UI, which then encrypts it via SecretStore
  • This is a one-time migration — subsequent launches read from the encrypted store

Security Trade-offs

  • This is not OS-native keychain integration (DPAPI on Windows, Keychain on macOS, libsecret on Linux) — that would be a follow-up
  • The AES key is stored in a separate file at 0600 permissions, so an attacker who can read ~/.local/share/GithubLauncher/\* can decrypt the token. This is still strictly better than plaintext in the app directory where any process or user-accessible path can read it
  • Memory exposure: the decrypted token lives in the .NET heap for the process lifetime. A memory-dump attack would still recover it — this protects against disk-level exposure only

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.

2 participants