Using an invisible helper sprite is perfectly acceptable and recommended for ease of use.
Otherwise, you may use the viewport expressions to get the edges of the screen and use those as a condition to keep the scroll event from running past the layout edges.
Haven't tried this yet, but combining this with clamp should keep the contents of the layer within bounds, correct or am I talking nonsense? Would this also take into consideration the current scale of the layout when zoomed in?
X : clamp( Self.X, ViewportLeft(Self.LayerName)+Self.Width/2 , ViewportRight(Self.LayerName) - Self.Width/2)
Y : clamp( Self.Y, ViewportTop(Self.LayerName)+Self.Height/2 , ViewportBottom(Self.LayerName) - Self.Height/2)