From 68a8d692f03ddc48aaf70399bfb1cc7584e21810 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Fri, 6 Nov 2026 09:18:57 +0000 Subject: A feed must not be able to spend the server's token The security review of the last commit found a confused deputy in it, and it was right. The bearer was attached on origin alone, and `episode.mediaUrl` comes out of a feed -- so a publisher could set an enclosure URL on the skip server's own origin, match the origin check exactly, and have the app issue an authenticated GET to a path of their choosing, storing whatever came back as that episode's audio. Confirmed by writing the test before the fix: it failed. Provenance is the first gate now and origin is the second. `authenticated = true` is passed only for the URL `ServerAudio.resolve` returned, never for one that arrived in a feed, and the origin check still has to agree -- so a server answer naming some other host is fetched without credentials rather than sending them there. Both gates have a test, including the case where they disagree, and OutboundRequestsTest pins that exactly one call site in the app is authenticated. **Chasing the coverage gate honestly turned up a real bug**, which is the second time this week it has earned its keep. Rather than file a third COVERAGE_EXCEPTION I went to see what the uncovered lines in EpisodeDownloader actually were, and wrote a test for the empty-response branch. It failed: OkHttp hands back an empty body rather than null for a 204, so the existing `?: run { ... }` guard never fires, the copy writes nothing, and a **zero-byte file is renamed into place and recorded as DOWNLOADED**. That episode then fails to play and reads as the publisher's fault. The length is checked after the copy now, which catches a 204, an empty 200 and anything else that arrives with no bytes. Also tested while there: the media-type to file-extension mapping, which had none. Not cosmetic -- Media3 picks an extractor partly from the extension, so a .audio file holding mp4 bytes is a plausible way to get an episode that will not play. Parameters and capitals included, because feeds send "audio/mpeg; charset=utf-8" and "AUDIO/MPEG". 1059 tests, detekt 0, lint clean, coverage 94.09% -> 94.28% with no exception. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B --- .coverage-baseline | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to '.coverage-baseline') diff --git a/.coverage-baseline b/.coverage-baseline index e11965c..867d422 100644 --- a/.coverage-baseline +++ b/.coverage-baseline @@ -1 +1 @@ -94.09 +94.28 -- cgit v1.2.3