Skip to content

security(android): harden project trust boundaries - #98

Closed
RXWatcher wants to merge 54 commits into
Silo-Server:mainfrom
RXWatcher:security/android-project-hardening
Closed

security(android): harden project trust boundaries#98
RXWatcher wants to merge 54 commits into
Silo-Server:mainfrom
RXWatcher:security/android-project-hardening

Conversation

@RXWatcher

@RXWatcher RXWatcher commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two stacks in one branch, in commit order.

Performance and stability (29 commits, eaef12d549cae1ff — formerly #97)

  • bound and coalesce startup/home hydration across Android phone and TV
  • pause off-route detail enrichment and isolate playback-clock recomposition
  • make teardown and final-position persistence lifecycle- and identity-safe
  • preserve playback state across recreation and keep TV duration on the live clock

Security hardening (25 commits, af9f4a94b4a43575)

  • centralize origin validation and keep credentials, refreshes, and redirects on the authenticated Silo origin
  • require per-origin consent for cleartext HTTP and authenticate custom PiP actions
  • isolate EPUB WebView resources, sanitize active content, bound archive/network inputs, and contain identity-scoped storage paths
  • remove implicit legacy permissions and validate merged phone/TV manifests
  • pin and verify Gradle, GitHub Actions, native Dolby/libdovi provenance, dependency locks, checksums, and Rust OSV results

Stack

Rebase (2026-07-25)

Rebased from 428e9678 (#94) onto 5047c4d2, picking up #99 (progressive direct-play range resume) and #100 (FCM notification metadata).

Two conflicts, both real:

One new commit, build(android): refresh locks and verification metadata for the rebase#99 added the media3 test-utils/robolectric stack to :android-shared, which the branch's lock state (generated against the old base) fails closed on. Note for anyone regenerating: the hardening plan documents metadata-then-locks, but that order only works from a clean slate. With stale locks present nothing resolves, so locks have to be written first.

Verification (post-rebase)

  • 5,330 test executions across the four modules, debug and release: shared 1,546, android-shared 1,626, androidApp 918, androidTvApp 1,240
  • phone and TV debug APKs assembled
  • debug and release merged-manifest permission policies passed
  • --dependency-verification=strict passed; live Rust OSV query passed (no affected crates.io packages)
  • lock additions are feat(player): resume progressive direct play transparently after mid-stream transport failures #99's transitive test closure only, no new repositories

One failure, inherited from main and not caused by this branch: PlaybackApiTest."v3 start uses canonical endpoint and negotiation fields" fails on a clean origin/main checkout too. #99 added DIRECT_STREAM_RESUME_V1_FEATURE to the client's negotiated feature list without updating the test's expected list. This branch does not touch that file. It needs a one-line fix on main, separately from this PR.

Not included

The two CodeRabbit findings fixed in c8481f1c (failed final-position writes discarded instead of re-queued; Up Next countdown not cancelled on exit) exist only on the fork's main. They apply to the perf commits carried here and are not on this branch.

CI

  • intentionally skipped per maintainer request; verified locally

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 146 files, which is 46 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cebb6371-87bd-491e-8d43-bee6389a8812

📥 Commits

Reviewing files that changed from the base of the PR and between 5047c4d and b4a4357.

⛔ Files ignored due to path filters (1)
  • android-shared/src/native/dovi/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (146)
  • .github/workflows/android-build.yml
  • .github/workflows/release.yml
  • THIRD_PARTY_NOTICES.md
  • android-shared/build.gradle.kts
  • android-shared/gradle.lockfile
  • android-shared/libs/silo-dovi-bridge-2.3.1.aar
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/data/repository/RoomUserItemStateRepository.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/di/PlayerInfraModule.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/downloads/DownloadStorage.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/downloads/DownloadWorker.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/ebook/EbookLocalStateStore.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/io/LimitedStreams.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/network/CleartextConsentStore.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/pip/SiloPictureInPictureCoordinator.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/player/AuthenticatedDataSourceFactory.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/player/FinalPlaybackPositionWriter.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/player/MediaAuthInterceptor.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/player/MediaAuthSession.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/player/PipActionCapability.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/player/PlaybackSessionLifecycle.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/player/PlayerOkHttpClient.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/player/SiloPlaybackService.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/startup/StartupWarmup.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/store/SafePathSegment.kt
  • android-shared/src/androidMain/kotlin/org/siloserver/silo/common/store/ScopedJsonFileStore.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/data/repository/RoomUserItemStateRepositoryTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/downloads/DownloadStorageTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/downloads/MediaStorePublicDownloadStoreCollisionTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/downloads/OfflineMediaResolverTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/io/LimitedStreamsTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/network/CleartextConsentStoreTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/player/AuthenticatedDataSourceFactoryTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/player/FinalPlaybackPositionWriterTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/player/MediaAuthInterceptorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/player/PipActionCapabilityTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/player/PlaybackSessionLifecycleLoggingTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/startup/StartupHomeHydrationTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/startup/StartupWarmupSourceTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/store/SafePathSegmentTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/siloserver/silo/common/store/ScopedJsonFileStoreTest.kt
  • android-shared/src/native/dovi/AndroidManifest.xml
  • android-shared/src/native/dovi/THIRD_PARTY_NOTICES.txt
  • android-shared/src/native/dovi/provenance.json
  • androidApp/build.gradle.kts
  • androidApp/gradle.lockfile
  • androidApp/src/androidMain/assets/reader/reflow/paginator.js
  • androidApp/src/androidMain/assets/reader/reflow/reader.html
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/di/AndroidModule.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/auth/ServerSetupScreen.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/auth/ServerSetupViewModel.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/detail/ItemDetailScreen.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/detail/ItemDetailViewModel.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/detail/SimilarRail.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/player/PlayerScreen.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/player/PlayerViewModel.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/reader/EpubBook.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/reader/ReaderFileCache.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/reader/reflow/EpubHtmlSanitizer.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/reader/reflow/EpubResourcePathHandler.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/reader/reflow/ReflowWebView.kt
  • androidApp/src/androidMain/kotlin/org/siloserver/silo/android/ui/screens/reader/reflow/ReflowableReader.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/AndroidManifestPolicyTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/SharedStartupWarmupSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/performance/MobileDetailOpenPerformanceSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/performance/MobilePlayerLifecyclePerformanceSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/auth/ServerSetupPersistenceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/detail/EpisodeRollupAccumulatorTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/player/MobilePlayerPresentationStateTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/player/PlayerScreenStartPositionTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/reader/EpubBookTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/reader/ReaderEngineHostSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/reader/ReaderFileCacheTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/reader/ReaderFileResolverTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/reader/ReflowWebViewEpubResourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/reader/reflow/EpubHtmlSanitizerTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/reader/reflow/EpubReflowSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/siloserver/silo/android/ui/screens/reader/reflow/EpubResourcePathHandlerTest.kt
  • androidTvApp/gradle.lockfile
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/di/AndroidTvModule.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/components/TvSkylineSectionFeed.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/auth/TvServerSetupScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/auth/TvServerSetupViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/detail/TvItemDetailScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/detail/TvItemDetailViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/home/HomeResumeRefreshPolicy.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/home/TvHomeScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/player/TvPlayerScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/screens/player/TvPlayerViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/siloserver/silo/tv/ui/shell/TvMainShell.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/SharedStartupWarmupSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/TvAndroidManifestPolicyTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/components/TvSkylineSectionFeedSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/performance/TvDetailOpenPerformanceSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/auth/TvServerSetupPersistenceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/home/HomeResumeRefreshPolicyTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/home/TvHomeScreenSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/player/TvPlayerPresentationStateTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/player/TvPlayerScreenStartPositionTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/screens/player/TvPlayerViewModelPlaybackPositionTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/siloserver/silo/tv/ui/shell/TvCollectionsPresenceCacheTest.kt
  • baselineprofile/gradle.lockfile
  • build.gradle.kts
  • docs/superpowers/plans/2026-07-22-android-home-request-coalescing.md
  • docs/superpowers/plans/2026-07-22-android-mobile-performance-remediation.md
  • docs/superpowers/plans/2026-07-22-android-startup-home-fanout-remediation.md
  • docs/superpowers/plans/2026-07-22-android-tv-performance-remediation.md
  • docs/superpowers/plans/2026-07-23-android-performance-audit-corrections.md
  • docs/superpowers/plans/2026-07-23-android-project-security-hardening.md
  • docs/superpowers/specs/2026-07-22-android-home-request-coalescing-design.md
  • docs/superpowers/specs/2026-07-22-android-mobile-performance-remediation-design.md
  • docs/superpowers/specs/2026-07-22-android-startup-home-fanout-remediation-design.md
  • docs/superpowers/specs/2026-07-22-android-tv-performance-remediation-design.md
  • docs/superpowers/specs/2026-07-23-android-project-security-hardening-design.md
  • gradle/libs.versions.toml
  • gradle/verification-metadata.xml
  • gradle/wrapper/gradle-wrapper.properties
  • libass-bridge/gradle.lockfile
  • scripts/build-dovi-aar.sh
  • scripts/check-build-supply-chain.sh
  • scripts/check-merged-manifest-permissions.sh
  • scripts/check-rust-osv.mjs
  • scripts/check-rust-osv.test.mjs
  • scripts/test-check-build-supply-chain.sh
  • settings-gradle.lockfile
  • shared/gradle.lockfile
  • shared/src/androidMain/kotlin/org/siloserver/silo/network/EncryptedTokenManagerImpl.kt
  • shared/src/androidUnitTest/kotlin/org/siloserver/silo/network/EncryptedTokenManagerScopeGenerationTest.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/di/RepositoryModule.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/model/section/HomeSectionHydration.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/network/AuthInterceptorImpl.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/network/AuthScopeSnapshot.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/network/HttpOriginPolicy.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/network/TokenManager.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/network/api/SectionApi.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/repository/HomeSectionsRequestGate.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/repository/SectionRepository.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/repository/port/UserItemStatePort.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/util/BoundedConcurrency.kt
  • shared/src/commonMain/kotlin/org/siloserver/silo/viewmodel/HomeViewModel.kt
  • shared/src/commonTest/kotlin/org/siloserver/silo/model/section/HomeSectionHydrationTest.kt
  • shared/src/commonTest/kotlin/org/siloserver/silo/network/HttpOriginPolicyTest.kt
  • shared/src/commonTest/kotlin/org/siloserver/silo/network/SiloAuthPluginPinTest.kt
  • shared/src/commonTest/kotlin/org/siloserver/silo/repository/HomeSectionsRequestGateTest.kt
  • shared/src/commonTest/kotlin/org/siloserver/silo/util/BoundedConcurrencyTest.kt
  • shared/src/commonTest/kotlin/org/siloserver/silo/viewmodel/HomeViewModelPerformanceSourceTest.kt
  • shared/src/commonTest/kotlin/org/siloserver/silo/viewmodel/HomeViewModelRefreshPolicySourceTest.kt

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

RXWatcher added 29 commits July 25, 2026 14:09
RXWatcher and others added 25 commits July 25, 2026 14:09
Rebasing onto main pulled in Silo-Server#99, which added the media3 test-utils and
robolectric test stack to :android-shared. The lock state on this branch was
generated against the old base, so every locked configuration that resolves
those artifacts failed closed — `./gradlew test` died at
:android-shared:generateDebugUnitTestStubRFile before compiling anything.

Regenerated with the procedure the hardening plan documents, but locks first:
with stale locks present the verification-metadata pass cannot resolve anything
to hash, so the documented metadata-then-locks order only works from a clean
slate.

    ./gradlew resolveAndLockAll --write-locks
    ./gradlew --write-verification-metadata sha256 <the six tasks>

Lock additions are Silo-Server#99's transitive test closure only (media3 test-utils,
test-utils-robolectric, transformer, muxer, inspector, plus mockito,
byte-buddy, truth, objenesis, androidx.test). No new repositories. The
verification metadata gains 827 lines and drops none; the extra components
beyond that closure are desktop/jvmstubs variants that `lockAllConfigurations`
resolves and the original pass, generated before locking was enabled, never
saw.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Quick104
Quick104 force-pushed the security/android-project-hardening branch from 3d5dd55 to b4a4357 Compare July 25, 2026 14:28
@RXWatcher

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by #108.

This branch turned out to be an older line of work that main has since moved past, rather than work still missing from it. Checking commit by commit rather than by subject line:

  • The leading commits cherry-pick to empty against current main — the content is already there, squashed into feat(android): performance/stability and transactional subtitles under different commit messages.
  • perf(tv): curb home startup fan-out conflicts against main's already-squashed version of the same change, and also touches source-grep tests that fix(ci): repair PlaybackApiTest and drop source-grep pseudo-tests #102 removed.
  • Net tree difference is +11,335 / −36,965: main carries ~37k lines this branch lacks, while most of the ~11k on this side is stale state (deleted tests, older versions of files that have since changed).
  • Spot checks agree — e.g. HomeViewModel on main already documents the coalescing behaviour this branch introduced.

Rebuilding it would mean reconstructing which fragments are genuinely absent and reconciling each against a squashed descendant, which is archaeology rather than a rebase. #108 carries the current state of all of it.

If a startup or playback performance regression turns up that traces back to something here, I'll port that specific behaviour deliberately rather than replay the branch.

@RXWatcher RXWatcher closed this Jul 26, 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