Files
offline-db/content/tests/test_watch_party_websocket_security.py
Barış Keserandgoogle-labs-jules[bot] a8f519f00e 🛡️ Sentinel: [HIGH] Fix WatchParty websocket authentication and connection rejection (#247)
🚨 Severity: HIGH
💡 Vulnerability: The `WatchPartyConsumer` accepted WebSocket connections from unauthenticated users, potentially allowing them to act as invisible 'ghost listeners' in private watch parties. In addition, when attempting to reject non-existent rooms, the consumer called `await self.close()` before `await self.accept()`, leading to ASGI protocol violations and unhandled connection terminations.
🎯 Impact: Unauthenticated users could eavesdrop on chat and video sync messages for watch parties they were not part of. Bad connections also crashed `channels` tests by prematurely dropping the handshake.
🔧 Fix: Updated the connection logic to raise `DenyConnection()` to securely and properly reject unauthorized or invalid connections before they are accepted. Also fixed the tests to pass a correctly formatted UUID to the WebSocket so the `URLRouter` doesn't drop the connection via an exception before it reaches the consumer.
✅ Verification: Ran `pytest apps/watchparty/` and `pytest content/tests/test_watch_party_websocket_security.py` locally and verified both passes. Created an explicit test for the unauthenticated case.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2026-03-10 21:35:37 +03:00

51 lines
1.9 KiB
Python

import pytest
from channels.testing import WebsocketCommunicator
from aniscrap_core.asgi import application
@pytest.mark.asyncio
@pytest.mark.django_db(transaction=True)
async def test_connect_to_non_existent_party():
# Attempt to connect to a non-existent party
room_name = "00000000-0000-0000-0000-000000000000"
communicator = WebsocketCommunicator(application, f"ws/watch-party/{room_name}/")
from channels.exceptions import DenyConnection
try:
connected, subprotocol = await communicator.connect()
assert connected is False, "Connection should be rejected for non-existent party"
except DenyConnection:
pass
await communicator.disconnect()
@pytest.mark.asyncio
@pytest.mark.django_db(transaction=True)
async def test_connect_unauthenticated():
from channels.db import database_sync_to_async
from django.contrib.auth import get_user_model
from apps.watchparty.models import Room
from content.models import Anime, Season, Episode
User = get_user_model()
@database_sync_to_async
def create_room():
user = User.objects.create_user(username='hostuser', password='password')
anime = Anime.objects.create(title='Test Anime')
season = Season.objects.create(anime=anime, number=1)
episode = Episode.objects.create(season=season, number=1, title='Test Ep')
room = Room.objects.create(episode=episode, host=user)
return room
room = await create_room()
communicator = WebsocketCommunicator(application, f"ws/watch-party/{room.uuid}/")
# We expect the connection to be rejected because user is unauthenticated
from channels.exceptions import DenyConnection
try:
connected, subprotocol = await communicator.connect()
assert connected is False, "Connection should be rejected for unauthenticated users"
except DenyConnection:
pass
await communicator.disconnect()