CanLex / tests /test_http_sessions.py
Beemer
Deploy: the 2026-09-21 code audit and its three verification rounds -- the quantity and relief allowlists, the five Canadian outcome readings, the IRPR s. 17 clock, the retrieval fixes and the performance work (approved_code 82525e1; curated baseline c32bd27 unchanged)
901bf8c
Raw History Blame Contribute Delete
5.56 kB
"""Verification fixes 2026-09-22 (F3), SEC-1: one client can no longer lock
every other client out of the HTTP transport.
With mcp 1.30.0 the session manager CanLex built had a global cap of 10,000
open sessions and no per-client limit, so one unauthenticated client could
fill it in about two minutes; every other client's `initialize` then got
503 'Too many open sessions' until the flood's sessions idled out (1800 s).
The critic's live probe, with the cap scaled from 10,000 to 5: one client's
six initializes [200, 200, 200, 200, 200, 503], another client's 503.
Now server.session_manager_with_timeout passes max_sessions=None (the 1.27.2
behaviour, bounded by the idle timeout) and server.http_app -- the app
__main__ serves -- adds canlex/ratelimit.py's SessionOpenLimit, on by
default. These tests drive the real stack (FastMCP's streamable-HTTP app,
the session manager, _Utf8Body, the cap) in-process with Starlette's
TestClient, the cap scaled down through CANLEX_SESSION_OPEN_LIMIT. Clients
are told apart by X-Forwarded-For, as Hugging Face's ingress supplies it.
python -m unittest tests.test_http_sessions
"""
import io
import logging
import unittest
from contextlib import redirect_stderr
from mcp.server.fastmcp import FastMCP
from starlette.testclient import TestClient
from canlex import ratelimit, server
INIT = {"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2025-03-26", "capabilities": {},
"clientInfo": {"name": "t", "version": "1"}}}
ACCEPT = {"Accept": "application/json, text/event-stream"}
FLOODER, OFFICER = "203.0.113.7", "198.51.100.9"
def toy():
srv = FastMCP("t")
@srv.tool()
def echo(word: str) -> str:
return word
return srv
class HttpSessionTests(unittest.TestCase):
def setUp(self):
for name in ("mcp", "httpx"):
log = logging.getLogger(name)
self.addCleanup(log.setLevel, log.level)
log.setLevel(logging.WARNING)
# _Utf8Body prints each new client tag once: keep the set as it was
seen = set(server._seen_clients)
self.addCleanup(lambda: (server._seen_clients.clear(),
server._seen_clients.update(seen)))
quiet = redirect_stderr(io.StringIO())
quiet.__enter__()
self.addCleanup(quiet.__exit__, None, None, None)
def serve(self, srv, environ):
err = io.StringIO()
with redirect_stderr(err):
app = server.http_app(srv, environ)
client = TestClient(app, base_url="http://127.0.0.1:8000")
client.__enter__() # runs the session manager's lifespan
self.addCleanup(client.__exit__, None, None, None)
return client, err.getvalue()
@staticmethod
def initialize(client, who):
return client.post("/mcp", json=INIT, headers=dict(ACCEPT, **{"X-Forwarded-For": who}))
def test_the_served_stack_has_no_global_cap_and_the_per_client_cap_on(self):
srv = toy()
_, err = self.serve(srv, {})
self.assertIsNone(server.SESSION_MAX_OPEN)
self.assertIsNone(srv._session_manager.max_sessions)
self.assertEqual(srv._session_manager.session_idle_timeout, server.SESSION_IDLE_SECONDS)
self.assertIn("no global session cap", err)
self.assertIn(f"at most {ratelimit.SESSION_OPEN_LIMIT} new sessions per client", err)
def test_a_flood_from_one_client_leaves_another_able_to_initialize(self):
srv = toy()
client, _ = self.serve(srv, {"CANLEX_SESSION_OPEN_LIMIT": "3"})
flood = [self.initialize(client, FLOODER) for _ in range(8)]
self.assertEqual([r.status_code for r in flood], [200] * 3 + [429] * 5)
self.assertIn("Too many new sessions", flood[-1].text)
officer = self.initialize(client, OFFICER)
self.assertEqual(officer.status_code, 200)
self.assertIn('"protocolVersion"', officer.text)
self.assertEqual(len(srv._session_manager._server_instances), 4)
# the flooder's own open sessions keep working: only new sessions are capped
sid = flood[0].headers["mcp-session-id"]
listed = client.post("/mcp", json={"jsonrpc": "2.0", "id": 2, "method": "tools/list"},
headers=dict(ACCEPT, **{"X-Forwarded-For": FLOODER,
"mcp-session-id": sid,
"mcp-protocol-version": "2025-03-26"}))
self.assertEqual(listed.status_code, 200)
self.assertIn('"echo"', listed.text)
def test_even_under_a_global_cap_one_client_cannot_fill_it(self):
"""The critic's scaled stand-in (a global cap of 5) with the per-client
cap at 3: before, the flood filled the table and the officer got 503."""
srv = toy()
client, _ = self.serve(srv, {"CANLEX_SESSION_OPEN_LIMIT": "3"})
srv._session_manager.max_sessions = 5
self.assertEqual([self.initialize(client, FLOODER).status_code for _ in range(6)],
[200] * 3 + [429] * 3)
self.assertEqual(self.initialize(client, OFFICER).status_code, 200)
def test_turned_off_the_flood_reaches_the_manager(self):
srv = toy()
client, err = self.serve(srv, {"CANLEX_SESSION_OPEN_LIMIT": "0"})
self.assertIn("no per-client cap on new sessions", err)
self.assertEqual({self.initialize(client, FLOODER).status_code for _ in range(5)}, {200})
if __name__ == "__main__":
unittest.main()