harden(ft,auth,net): cap transfers/frames, evict stale SRP, distrust XFF
CI / rust client (hh) (macos-latest) (push) Has been cancelled
CI / rust client (hh) (ubuntu-latest) (push) Has been cancelled
CI / rust coverage (push) Has been cancelled
CI / python server (3.10) (push) Has been cancelled
CI / python server (3.11) (push) Has been cancelled
CI / python server (3.12) (push) Has been cancelled
CI / headless e2e smoke (push) Has been cancelled
CI / dependency audit (push) Has been cancelled
CI / secret scanning (push) Has been cancelled
CI / rust client (hh) (macos-latest) (push) Has been cancelled
CI / rust client (hh) (ubuntu-latest) (push) Has been cancelled
CI / rust coverage (push) Has been cancelled
CI / python server (3.10) (push) Has been cancelled
CI / python server (3.11) (push) Has been cancelled
CI / python server (3.12) (push) Has been cancelled
CI / headless e2e smoke (push) Has been cancelled
CI / dependency audit (push) Has been cancelled
CI / secret scanning (push) Has been cancelled
M1: enforce the declared transfer size (clamped to MAX_SIZE) on chunk receipt in both the Rust and Python clients — a malicious sender can no longer grow the receive buffer unboundedly. M2: only honor X-Forwarded-For when TRUST_PROXY is set, so a direct client can't spoof a source IP to dodge the per-IP rate limiter. M3: evict unverified SRP sessions after a 60s TTL on each new handshake, preventing half-finished auths from exhausting memory. M4: drop WS frames larger than 256 KB before they hit the store or broadcast, bounding per-message memory and flood blast radius. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,13 @@ from .models import Message, UserSession
|
||||
from .helpers import get_client_ip, send_state, utcnow
|
||||
|
||||
|
||||
# Hard cap on a single relayed WS frame. The largest legitimate frame is one
|
||||
# Fernet-encrypted 64 KB file chunk (~120 KB after base64 + token overhead), so
|
||||
# 256 KB leaves headroom while bounding per-message memory and the 1000-message
|
||||
# store. Oversized frames are dropped, not stored or broadcast.
|
||||
MAX_FRAME_SIZE = 256 * 1024
|
||||
|
||||
|
||||
def generate_ws_token(user_id: str, secret: bytes) -> str:
|
||||
return hmac.new(secret, user_id.encode(), hashlib.sha256).hexdigest()
|
||||
|
||||
@@ -152,10 +159,16 @@ async def chat_ws(request: Request, ws: Websocket, app: Sanic) -> None:
|
||||
if data is None:
|
||||
break
|
||||
|
||||
text = str(data)
|
||||
# Drop oversized frames before they reach the store/broadcast: this
|
||||
# bounds memory and stops a single client from flooding the room.
|
||||
if len(text) > MAX_FRAME_SIZE:
|
||||
continue
|
||||
|
||||
app.ctx.session_store.update_activity(user_id)
|
||||
|
||||
message = Message(
|
||||
text=str(data),
|
||||
text=text,
|
||||
username=session.username,
|
||||
)
|
||||
app.ctx.message_store.add(message)
|
||||
|
||||
Reference in New Issue
Block a user