Skip to content

Harden token storage, login WebView, and update trust boundaries - #31

Merged
spacealtctrl merged 3 commits into
spacealtctrl:mainfrom
cutieway:security-hardening
Nov 4, 2026
Merged

spacealtctrl merged 3 commits into
spacealtctrl:mainfrom
cutieway:security-hardening

Conversation

@cutieway

Copy link
Copy Markdown
Contributor

Summary

Hardens the Discord token lifecycle and the trust boundaries that can expose or misuse it. This is a security-hardening change, not a vulnerability disclosure.

1. Seal the token with Android Keystore and redact credentials

  • Stores the token as AES-256-GCM ciphertext under a non-exportable Android Keystore key; the legacy plaintext preference is migrated and removed.
  • Decrypts only on demand for the gateway and clears the key with the token on sign-out and terminal auth rejection.
  • Clears WebView data before wiping the app, in an order that prevents a backup from restoring a session that no longer has a key.
  • Redacts Authorization, Cookie, Set-Cookie, token-shaped strings, signed URL parameters, and credential fields from logs and crash reports.

2. Constrain the Discord login WebView to its trust boundary

  • Trusts only canonical Discord HTTPS origins and extracts the token only from the /app route.
  • Reads the token through a same-origin iframe (Discord no longer exposes localStorage to the top window), rejects malformed tokens, and fails closed on capture timeout.
  • Cleans up on every terminal path, clears cookies/storage when the sheet closes, and keeps a manual paste fallback isolated in this commit for old WebViews that cannot render the login page.

3. Validate gateway resume URLs and update artifacts

  • Validates gateway resume endpoints against Discord-owned hosts, fixes the wss scheme, adds the protocol query, rejects user info/ports/queries/fragments, and resolves the token through a provider so it is not captured in a closure.
  • Determines updates from the project's GitHub releases, requires an HTTP 404 to mean up-to-date, and validates the asset URL, sha256: digest, and size cap before download.
  • Verifies the downloaded APK's package name and signer against the installed app before the installer is opened; rejects multi-signer and foreign-signer binaries.
  • Enables a network security config with cleartext disabled and keeps only system CAs, and removes the MEDIA_CONTENT_CONTROL privileged permission.

Testing

Per commit:

  • ./gradlew :app:testDebugUnitTest :app:assembleDebug :app:assembleDebugAndroidTest

Final branch:

  • ./gradlew :app:lintDebug — passed.
  • ./gradlew :app:assembleRelease — passed.
  • ./gradlew :app:connectedDebugAndroidTest — Gradle's task hung on this device's package-installer confirmation UI. The same debug and test APKs were installed manually and run directly with am instrument; all 13 instrumentation tests passed (TokenStorageTest, UpdateIdentityTest).
  • Verified UpdatePolicy accepts the current upstream v1.5.4 release asset and its published SHA-256 digest.

Scope

  • No documentation, workflow, release, or production dependency changes; only test-only dependencies were added.
  • UpdatePolicy continues to use spacealtctrl/DiscordRP releases; this branch does not point updates at a fork.
  • Behavior changes: token cleanup is fail-closed, terminal auth failures clear the session, the login WebView is limited to Discord origins, and updates signed with another key or missing a digest are rejected. Signing-key rotation is intentionally not supported yet.

Store the Discord token as an AES-256-GCM blob under an Android Keystore key, migrate the legacy plaintext preference, and delete the key whenever the credential is cleared so an escaped blob cannot be reopened. Read the token on demand instead of caching plaintext.

Redact authorization headers, cookies, token-shaped strings, signed URLs, and credential fields from logs and crash reports. Clear WebView storage and the sealed token before a sign-out wipe, and stop boot-start failures from logging raw exception text.

Add instrumentation coverage for migration, isolated test storage, key deletion, corrupted blobs, and legacy cleanup.
Load and navigate only canonical https://discord.com origins, and extract the token only from /app. Read storage through a same-origin iframe because Discord removes the storage API from the top window, then remove the iframe in the same turn.

Tear the login WebView down in an order that cannot leave a live document behind: stop loading, replace it with about:blank, wait for the commit, clear storage, and only then flush cookies. Fail closed and destroy the view if the blank page never commits. Validate pasted token shape before the handshake, reject failed handshakes instead of crashing, and keep the manual-token and repaint fallbacks for WebViews that cannot render the login page.

Add JVM coverage for origin matching, token shape, and cleanup ordering/timeout.
Accept only Discord gateway hosts for resume endpoints and re-append the gateway protocol query, since the endpoint arrives over the gateway itself. Read the token through a provider so the engine does not retain a plaintext copy, clear the credential on terminal auth rejection or an unreadable blob, and drop it on an unauthorized API response.

Require update releases from the project's GitHub feed, validate the asset URL at parse and download time, cap the transfer size, verify the published SHA-256 digest, and check the archive package id and signing certificate against the installed app before invoking the installer. Disable cleartext traffic, drop the privileged media permission, and stop exporting the boot receiver.

Add JVM tests for gateway endpoints and update URL/digest policy, plus instrumentation fixtures for foreign, multi-signer, and unsigned update APKs.
@spacealtctrl
spacealtctrl merged commit ca1458e into spacealtctrl:main Nov 4, 2026
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