From 23fb6f52b01800a00092ba8f2c5d9ac15609a99c Mon Sep 17 00:00:00 2001 From: Kayos Date: Tue, 26 May 2026 21:31:07 -0700 Subject: [PATCH] vc=69: audit-fix sprint round 2 (regressions on round 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-2 audit caught four real regressions on round-1 fixes plus a handful of MEDs. This sprint fixes them. H1 — FeedRefreshWorker exception class Round 1 wrapped subscriptionFeed in try/catch IOException, but UniFFI generates StrawcoreException (kotlin.Exception, not IOException). The retry path was dead code. Catch StrawcoreException.Network instead — the variant our error.rs maps NetworkError::Transport into. H2 — enrichJob terminal emit cancellation race withContext(Dispatchers.Default) { mergeFromCache(...) } has no suspension points so a cancel arriving mid-merge isn't observed until the next suspending call. Without a guard, the non-suspending _ui.update lands AFTER clearInMemoryCache() and resurrects the cleared items. Add coroutineContext.ensureActive() after each withContext hop, before the emit. Applied on both the refresh terminal emit and the enrich terminal emit. H6 — enrichVisibleItems shows stale subscriptions The channelsSnapshot captured at refresh-end is ~2s stale by the time the enrich terminal emit runs. If the user unsubscribed from X in that window, X's items still appear on the feed for one frame. Re-read Subscriptions at the terminal step and intersect with the snapshot. R-H3 — extract_channel_id substring match Round 1 used trimmed_lower.find(prefix) which matches ANY position. evil.com/?redir=https://www.youtube.com/channel/UCxxx silently rewrote to the embedded channel ID. strip_prefix() anchors at byte 0. ASCII-only prefix means byte indices align in trimmed_lower vs trimmed. R-H2 — String::from_utf8 silent-drop YouTube ships mojibake titles in the wild. Strict from_utf8 returned None on any bad byte, dropping the entire channel from the feed with only a quiet None. Switch to from_utf8_lossy — quick-xml tolerates U+FFFD replacement chars and the per-entry skip-on-empty handles broken entries. R-H1 — read_capped_body per-chunk size sanity HTTP allows arbitrarily large single chunks. Reject any chunk exceeding the whole body cap before adding it to the buffer, so a hostile server can't get us to allocate a hyper Bytes larger than the cap. M3 — Avatar URL validation ch.avatar is extractor-emitted; a poisoned channel page could ship data:image/svg+xml,...