Home Features Docs Blog Security Examples FAQ
Releases - Part 3 of 4

djust 0.2.1: WebSocket Security Hardening with Three Layers of Defense

djust Team | | 4 min read
Three layered shields representing djust 0.2.1 WebSocket security defense layers

Overview

djust 0.2.1 is a security-focused release that hardens the WebSocket event dispatch layer. Every LiveView that accepts user events over WebSocket is now protected by three layers of defense, all enabled by default in strict mode.

If you're running 0.2.0, upgrading requires one change: add @event_handler to your handler methods.

The Problem

In 0.2.0, any public method on a LiveView could be called from the client via WebSocket. A malicious client could dispatch events like __init__, _private_method, or any other method name, and getattr() would resolve it. This is a class of vulnerability common in RPC-style frameworks.

Three Layers of Defense

Layer 1: Event Name Guard

Before getattr() is ever called, event names are validated against a strict regex pattern:

^[a-z][a-z0-9_]*$

This blocks dunders (__init__), private methods (_secret), and any malformed names. Fast, cheap, and catches the obvious attacks.

Layer 2: @event_handler Allowlist

Only methods explicitly decorated with @event_handler can be called via WebSocket:

from djust import LiveView, event_handler

class CounterView(LiveView):
    template_name = "counter.html"

    def mount(self, request):
        self.count = 0

    @event_handler
    def increment(self):
        self.count += 1

    @event_handler
    def decrement(self):
        self.count -= 1

    def _internal_helper(self):
        # Not callable from client - no decorator
        pass

The security mode is configurable via LIVEVIEW_CONFIG:

  • "strict" (default) — only @event_handler methods are callable, undecorated calls raise an error
  • "warn" — undecorated methods still work but log a deprecation warning (use for gradual migration)
  • "open" — no allowlist check (not recommended for production)
# settings.py
LIVEVIEW_CONFIG = {
    "event_security": "strict",  # default
}

Layer 3: Server-Side Rate Limiting

A token bucket algorithm limits event throughput per connection. Expensive handlers can set tighter limits with @rate_limit:

from djust import LiveView, event_handler, rate_limit

class SearchView(LiveView):
    template_name = "search.html"

    @event_handler
    @rate_limit(rate=2, burst=5)  # 2 per second, burst of 5
    def search(self, query):
        self.results = Product.objects.filter(name__icontains=query)[:20]

Connections that exceed the rate limit are disconnected with WebSocket close code 4429. After disconnection, a reconnection cooldown (default 5 seconds) prevents rapid reconnect loops.

Per-IP Connection Limits

A process-level IPConnectionTracker enforces a maximum number of concurrent WebSocket connections per IP address (default: 10). This prevents a single client from opening hundreds of connections to exhaust server resources.

# settings.py
LIVEVIEW_CONFIG = {
    "rate_limit": {
        "max_connections_per_ip": 10,     # default
        "reconnect_cooldown": 5,           # seconds after rate-limit disconnect
    },
    "max_message_size": 65536,             # 64KB default
}

The tracker supports X-Forwarded-For headers for deployments behind a reverse proxy.

New APIs

  • @event_handler — marks a method as callable via WebSocket
  • is_event_handler(func) — check if a function is decorated
  • @rate_limit(rate, burst) — per-handler rate limiting
  • _allowed_events class attribute — bulk allowlisting escape hatch for views with many handlers

Upgrading from 0.2.0

  1. Install the update:
    pip install --upgrade djust==0.2.1
  2. Add @event_handler to every method that handles client events:
    from djust import event_handler
    
    # Add this decorator to all your handler methods
    @event_handler
    def my_handler(self, ...):
        ...
  3. Optionally, start in "warn" mode to find undecorated handlers without breaking anything:
    LIVEVIEW_CONFIG = {"event_security": "warn"}
    Then switch to "strict" once all handlers are decorated.

What's Next

With the event dispatch layer secured, the next release will focus on new features. Follow the project on GitHub for updates.

Share this post

Related Posts