diff --git a/pkg/spxrpc/labels_test.go b/pkg/spxrpc/labels_test.go index c01f2dc4..7d3ecc3c 100644 --- a/pkg/spxrpc/labels_test.go +++ b/pkg/spxrpc/labels_test.go @@ -144,11 +144,22 @@ func blobReq(did, cid string) (echo.Context, *httptest.ResponseRecorder) { } func TestHandleGetVideoBlob_LabelerGating(t *testing.T) { - t.Run("spoofed did fails as not-found", func(t *testing.T) { + t.Run("clip by another user (non-owner did) serves", func(t *testing.T) { + // The blob is content-addressed; a clip references it with the + // clipper's did, which doesn't own a track. It still serves. s, _ := setupBlobTest(t) + c, rec := blobReq(testOtherUser, testContentCID) + require.NoError(t, s.HandleGetVideoBlob(c)) + require.Equal(t, http.StatusOK, rec.Code) + }) + + t.Run("banned content owner blocks even a non-owner did", func(t *testing.T) { + // A clip can't bypass the original owner's ban. + s, m := setupBlobTest(t) + putLabel(t, m, testOwner, atproto.LabelTakedown) c, _ := blobReq(testOtherUser, testContentCID) he := requireHTTPError(t, s.HandleGetVideoBlob(c)) - require.Equal(t, http.StatusNotFound, he.Code) + require.Equal(t, http.StatusForbidden, he.Code) }) t.Run("banned owner is forbidden", func(t *testing.T) { diff --git a/pkg/spxrpc/place_stream_playback_getvideo.go b/pkg/spxrpc/place_stream_playback_getvideo.go index 8a20789b..cafa8db8 100644 --- a/pkg/spxrpc/place_stream_playback_getvideo.go +++ b/pkg/spxrpc/place_stream_playback_getvideo.go @@ -101,28 +101,22 @@ func (s *Server) HandleGetVideoBlob(c echo.Context) error { } did := parsedDID.String() - // Labeler enforcement. If this CID is a known content blob, the - // supplied `did` must actually own a track in it (a spoofed DID has - // no matching MediaTrack and fails as not-found), and that account - // must not be banned. CIDs with no MediaTrack — per-track init - // segments, or simply unknown CIDs — serve unguarded. + // Labeler enforcement. If this CID is a known content blob, gate it on + // its content owners — the track owners — not on the requesting `did`. + // The blob is content-addressed and shared: a clip of another user's + // video references the same blob with the clipper's did, which doesn't + // own a track. So `did` is for egress accounting only; if any of the + // blob's track owners is banned, the content is unavailable. (A clip's + // own labels/ban are enforced at getVideoPlaylist, the playback entry + // point.) CIDs with no MediaTrack — per-track init segments, or simply + // unknown CIDs — serve unguarded. tracks, err := s.model.GetMediaTracksByBlob(ctx, cid) if err != nil { log.Error(ctx, "playback: GetMediaTracksByBlob failed", "cid", cid, "error", err) return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - if len(tracks) > 0 { - owned := false - for _, t := range tracks { - if t.RepoDID == did { - owned = true - break - } - } - if !owned { - return echo.NewHTTPError(http.StatusNotFound, "BlobNotFound") - } - if banned, err := s.accountBanned(did); err != nil { + for _, t := range tracks { + if banned, err := s.accountBanned(t.RepoDID); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } else if banned { return echo.NewHTTPError(http.StatusForbidden, "VideoUnavailable")