Skip to content

fix(uagents): prevent sync-query future leak and cross-talk in ASGI server#910

Open
PrazwalR wants to merge 1 commit into
fetchai:mainfrom
PrazwalR:fix/asgi-sync-query-leak
Open

fix(uagents): prevent sync-query future leak and cross-talk in ASGI server#910
PrazwalR wants to merge 1 commit into
fetchai:mainfrom
PrazwalR:fix/asgi-sync-query-leak

Conversation

@PrazwalR

@PrazwalR PrazwalR commented Jul 6, 2026

Copy link
Copy Markdown

Summary

The ASGI /submit handler leaked entries in the server-side sync-response map
(ASGIServer._queries) and could cross-wire concurrent sync queries.

_queries[env.sender] was created before the envelope signature was verified
and before the target was confirmed routable, and it was never removed on the
failure paths:

  • signature verification failed → early return (entry leaked)
  • target not routable → early return (entry leaked)
  • sync request timed out waiting for a reply → no cleanup (entry leaked)

Only the happy path (handler replies) removed the entry, via context.send_raw.

Impact

  • Unauthenticated memory-exhaustion DoS. /submit needs no auth. A caller
    sending sync requests (x-uagents-connection: sync) with a user... sender
    (signature check skipped) and any unroutable target adds a permanent dict entry
    per request. The key is env.sender, an arbitrary wire string, so each entry's
    size is attacker-controlled. The dict never shrinks.
  • Concurrent-query cross-talk. _queries was keyed by sender only, so two
    in-flight sync queries from the same caller collided (second overwrote the
    first's Future).

Fix

  • Register the response Future only after signature + routing checks pass.
  • Key _queries by (sender, session) so concurrent queries never collide.
  • Remove the entry in a finally around the sync wait (happy path already
    deletes it in context.send_raw, so pop(..., None) is a safe no-op there).
  • Resolve replies by (sender, session) in context.send_raw; update type hints.

Files: asgi.py, context.py, agent.py (type hints), tests/test_server.py
(sync helper updated for the tuple key).

Testing

  • Existing suites pass: test_server, test_context, test_agent,
    test_protocol, test_msg_verify.
  • Verified end-to-end (asgi → message queue → consumer → on_message handler →
    ctx.send → future resolved), plus failure/timeout/concurrency/load cases:
    every failure, timeout, and unroutable path leaves _queries empty; concurrent
    same-sender / different-session queries each receive their own reply with no
    cross-talk.
  • ruff check clean on changed files.

🤖 Generated with Claude Code

…erver

The ASGI /submit handler registered a response Future in _queries before verifying the envelope signature and confirming the target is routable, and never removed it on the verify-fail, unroutable, or sync-timeout paths. An unauthenticated caller could grow the dict without bound (key = attacker-controlled env.sender) — an unbounded-memory DoS. The map was also keyed by sender only, so concurrent sync queries from the same caller collided.

- Register the Future only after signature + routing checks pass.
- Key _queries by (sender, session) and clean it up in a finally.
- Resolve replies by (sender, session) in context.send_raw.
- Update _queries type hints accordingly.
Copilot AI review requested due to automatic review settings July 6, 2026 10:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the ASGI /submit sync-query mechanism by preventing _queries map leaks on failure/timeout paths and eliminating cross-talk between concurrent sync queries from the same sender by keying futures with (sender, session).

Changes:

  • Move sync Future registration to occur only after signature verification and routing checks pass.
  • Key pending sync-query futures by (sender, session) and resolve replies using that tuple key.
  • Ensure _queries cleanup via finally so timeouts/cancellations don’t leak entries.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
python/src/uagents/asgi.py Registers sync-query futures later, uses (sender, session) keys, and guarantees cleanup in finally.
python/src/uagents/context.py Updates send_raw typing/docs and resolves sync replies via (sender, session) to avoid cross-talk.
python/src/uagents/agent.py Updates _queries type hints to reflect tuple-keyed futures.
python/tests/test_server.py Updates test helper to locate tuple-keyed query futures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 27 to 36
async def mock_process_sync_message(self, sender: str, msg: Model):
while True:
if sender in self.agent._server._queries:
self.agent._server._queries[sender].set_result(
key = next(
(k for k in self.agent._server._queries if k[0] == sender), None
)
if key is not None:
self.agent._server._queries[key].set_result(
(msg.model_dump_json(), Model.build_schema_digest(msg))
)
return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants