straw/strawApp
Kayos 2fd439cac8 Straw audit-fix sprint: CRIT-1 + HIGH-9 + targeted MED
Phase L. Triages findings from the Opus max-effort audit
(memory/2026-05-23-night3-straw later; agent report inline).

CRIT-1: ACTION_SEND URL extracted from arbitrary text/plain share now
  re-validated via URI host check before reaching NewPipeExtractor.
  Manifest scheme also validated for VIEW. Regex broadened to accept
  music.youtube.com + youtube-nocookie. Host set expanded.

HIGH: usesCleartextTraffic="true" removed from manifest (was redundant
  with network_security_config + misleading to reviewers).

HIGH: NewPipeDownloader hardens header copy — runCatching around addHeader
  to survive poisoned response headers with \r/\n. Explicit UA added AFTER
  upstream header copy so it wins. Switched to OkHttp 5 extension form
  for toRequestBody.

HIGH: Bounded response bodies via new util `cappedString(maxBytes)`. Caps
  at 8MiB (NewPipeExtractor), 1MiB (SponsorBlock), 256KiB (RYD). Defends
  against OOM via gigabyte response. Partial defense — chunked transfers
  without Content-Length still streamed up to cap.

HIGH: HistoryStore / SettingsStore / SubscriptionsStore moved from
  non-atomic `_flow.value = next` to `_flow.updateAndGet { ... }`. Closes
  the read-modify-write race that could drop concurrent writes.

HIGH: VideoDetailViewModel.load had a dead-code `if (...)` block with no
  body — falls through every call. Replaced with a real early-return on
  detail-already-loaded.

HIGH: ChannelViewModel picked `info.tabs.firstOrNull()` which is YouTube's
  curated "Home" tab. Switched to `firstOrNull { ChannelTabs.VIDEOS in
  contentFilters }` with fallback. Channel screen now shows actual videos.

HIGH: PlayerScreen SponsorBlock skip loop hardened:
  - dedup skipped UUIDs so re-listen doesn't fight the user
  - poll 250ms → 150ms reduces sponsor leak through buffering
  - `isPlaying` → `playbackState != IDLE/ENDED` so buffering doesn't miss
  - clamp seek away from duration boundary (prevents past-end jank)
  - filter POI-style point segments (start ≈ end)

MED: ExoPlayer pause on app background via Lifecycle.Event.ON_STOP
  observer. Was silently playing audio with no MediaSession when app
  was backgrounded.

MED: Log.d/Log.w gated behind BuildConfig.DEBUG via new strawLogD/strawLogW
  inline helpers in util/Log.kt. Lambda body skipped in release too.
  Log.i remains unguarded for user-quotable events. RydClient +
  SponsorBlockClient + PlayerScreen updated.

MED: Hardcoded version "v0.1.0" in StrawHome replaced with
  BuildConfig.VERSION_NAME. Now reads "v0.1.0-day1" matching ProjectConfig.

MED: Triplicated formatCount/formatViews/formatDuration extracted to
  util/Formatting.kt. Search/Channel/VideoDetail import the shared
  functions.

MED: SponsorBlockClient.buildJsonArray uses kotlinx-serialization Json
  encoder instead of hand-rolled string concat — defends against future
  user-typed category names breaking the URL.

MED: Description regex passes capped to 20k input chars before stripHtml
  on detail screen — defends against ANR on multi-MB descriptions.

Deferred to phase M (not in this sprint):
  - R8 + ProGuard rules for release builds (isMinifyEnabled=true)
  - ExoPlayer hoisting into PlayerViewModel (AndroidViewModel)
  - DI / Koin to replace `error("not initialized")` singleton accessors
  - String resources / i18n
  - rememberSaveable nav stack for process-death restore
  - onNewIntent override for in-running YouTube URL shares
  - certificate pinning on RYD endpoint
2026-05-23 20:23:34 -07:00
..
src/main Straw audit-fix sprint: CRIT-1 + HIGH-9 + targeted MED 2026-05-23 20:23:34 -07:00
build.gradle.kts Straw phase H: Settings screen + SponsorBlock category toggles 2026-05-23 19:51:43 -07:00