Skip to content

Fix crash bugs and harden security found in full-app scan#30

Merged
chamika merged 1 commit into
mainfrom
fix-bug-security-scan
Jul 2, 2026
Merged

Fix crash bugs and harden security found in full-app scan#30
chamika merged 1 commit into
mainfrom
fix-bug-security-scan

Conversation

@chamika

@chamika chamika commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Full scan of the app (all Kotlin sources, manifest, build config) for bugs and security issues. This PR fixes the confirmed, high-confidence findings; lower-severity observations that were deliberately left unchanged are listed at the bottom.

Bug fixes

QuickConnect sign-in (SignInViewModel, CredentialsFragment)

  • Codes with leading zeros were displayed wrong. Integer.valueOf(response.content.code) converted the server's string code to an Int, so 012345 was shown as 123 45 — a code the server never accepts. The code now stays a String end to end (verified against the SDK: QuickConnectResult.code is String).
  • Short codes crashed the app. code.substring(0, 3) threw StringIndexOutOfBoundsException for codes under 3 characters. Formatting is now length-safe (formatQuickConnectCode, covered by new unit tests).
  • Any polling error crashed the app. The polling coroutine had no exception handling, so a network blip or an expired QuickConnect secret (the server errors once it lapses) threw an unhandled exception in viewModelScope and killed the process. It now catches, records to Crashlytics, and shows "Unavailable".
  • Polling never stopped after login and restarted a second loop on every fragment recreation. The loop now exits once authenticated, and startQuickConnect is idempotent while a poll is active.

Media session (DashTuneSessionCallback)

  • onSetRating crashed on non-heart ratings. The blind rating as HeartRating cast threw ClassCastException for any connected controller sending a different Rating subtype (the service is exported, as AAOS requires). It now returns ERROR_NOT_SUPPORTED.
  • onSetMediaItems could produce start index -1. The non-audiobook single-item path didn't coerce indexOfFirst() like the audiobook path does; a stale cache mismatch would yield an invalid start index.

Playback service (DashTuneMusicService)

  • serviceScope was never cancelled in onDestroy, leaking any in-flight coroutines (position reports, sync) past service destruction.
  • Malformed preference values crashed onCreate. cache_size was parsed with toLong() and bitrate with toInt(); garbage in prefs (e.g. from a bad restore) would crash the service at startup. Now toLongOrNull()/toIntOrNull() with sane fallbacks (also fixed in MediaItemFactory.streamingUri).

Others

  • AlbumArtContentProvider.uriMap is written from media-session threads and read from binder threads with no synchronization — now a ConcurrentHashMap.
  • JellyfinAccountManager.storeAccount called .equals() on getUserData(...), which can return null → NPE. Now a null-safe comparison.
  • CredentialsFragment did an unchecked as InputMethodManager cast on a nullable lookup after a suspend call — NPE if the activity is gone by then.

Security hardening

  • android:allowBackup="false". The auth token lives in AccountManager (not backed up), but the Room library cache, playlist history, and server details were extractable via device backup. Nothing in the backup is worth restoring, so backup is disabled outright.

Reviewed, deliberately not changed

  • android:usesCleartextTraffic="true" — required for HTTP Jellyfin servers on LANs; removing it would break common setups.
  • Stream URLs do not embed the access token (verified in SDK sources: getUniversalAudioStreamUrl adds no ApiKey param; auth is header-only), so the download index retains no credentials after logout.
  • Exported activities/service/provider — all required by AAOS (Media Center intents, system UI album art).
  • Authenticator.getAuthToken returns the account "password" (always "") as the token — effectively dead code since nothing calls it; changing it to hand out the real token would widen exposure, so it was left alone.

Testing

  • ./gradlew :automotive:testDebugUnitTest — all existing tests pass, plus new tests for QuickConnect code formatting (leading zeros, short codes, odd lengths).

🤖 Generated with Claude Code

Bugs:
- QuickConnect: stop converting the server code to Int, which dropped
  leading zeros and displayed a code the server never accepts; format
  it defensively (old code crashed on codes shorter than 3 chars)
- QuickConnect: wrap the polling coroutine in try/catch so a network
  error or expired secret shows "Unavailable" instead of crashing the
  app; stop polling once authentication succeeds and guard against
  duplicate polling loops on fragment recreation
- onSetRating: reject non-HeartRating ratings instead of crashing with
  ClassCastException (reachable by any connected controller)
- onSetMediaItems: coerce indexOfFirst() == -1 to 0 so a stale cache
  can't produce an invalid start index
- AlbumArtContentProvider: use ConcurrentHashMap for the URI map, which
  is written from session threads and read from binder threads
- DashTuneMusicService: cancel serviceScope in onDestroy to stop leaked
  coroutines; parse cache_size/bitrate prefs with toLongOrNull/
  toIntOrNull instead of crashing on malformed values
- JellyfinAccountManager: null-safe server comparison in storeAccount
  (getUserData can return null)
- CredentialsFragment: null-safe InputMethodManager lookup after the
  suspend login call

Security:
- Disable android:allowBackup so the cached library DB and preferences
  cannot be extracted via device backup

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chamika chamika merged commit a5bbc37 into main Jul 2, 2026
1 check passed
@chamika chamika deleted the fix-bug-security-scan branch July 2, 2026 16:35
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