Skip to content

Fix session scoping in response middlewares#681

Open
matdev83 wants to merge 1 commit intodevfrom
codex/fix-cross-message-data-leaks-jlbpv0
Open

Fix session scoping in response middlewares#681
matdev83 wants to merge 1 commit intodevfrom
codex/fix-cross-message-data-leaks-jlbpv0

Conversation

@matdev83
Copy link
Owner

@matdev83 matdev83 commented Nov 7, 2025

Summary

  • derive stable session identifiers for the loop detection middleware to avoid mixing streaming content between clients
  • scope edit-precision state updates by resolved session or stream identifiers and ignore responses we cannot attribute safely
  • add unit coverage to ensure anonymous streaming sessions remain isolated across both middleware implementations

Testing

  • python -m pytest

Codex Task


Note

Derives stable session/stream keys for loop detection and edit-precision middlewares, isolates anonymous streams, skips unscoped updates, and adds unit tests for these behaviors.

  • Core Services:
    • src/core/services/response_middleware.py (LoopDetectionMiddleware):
      • Add _resolve_session_key to derive stable keys from session_id, context/metadata fields, or stop_event (with anonymous alias via uuid4).
      • Accumulate content by resolved key; include session_id in loop error details.
      • Add _cleanup_session_state to clear anonymous/ephemeral state when streaming ends.
      • Import uuid4 and track _anonymous_session_aliases.
    • src/core/services/edit_precision_response_middleware.py:
      • Add _resolve_session_key using common identifier fields; skip state updates if no key found.
      • Scope pending/active maps and stream de-duplication to resolved session key.
      • Update logging to use resolved key.
  • Tests:
    • Edit Precision: Add tests for using stream_id when no session_id, and skipping updates with no identifiers.
    • Loop Detection: Add test ensuring anonymous streaming sessions remain isolated and do not leak accumulated content.
    • Minor test adjustments to validate new scoping behavior.

Written by Cursor Bugbot for commit 1c57d35. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

Release Notes

  • Refactor

    • Enhanced session identification to improve reliability of cross-context session tracking and state management.
    • Improved stream isolation to prevent data leakage between concurrent streams and ensure data integrity.
    • Optimized session state cleanup for better resource management.
  • Tests

    • Added comprehensive test coverage for stream isolation scenarios and edge cases where identifiers are missing.

@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The changes introduce session key resolution mechanisms across two middleware classes to derive stable session identifiers from multiple possible fields (session_id, stream_id, id, request_id, conversation_id, thread_id, message_id) and update state management to use these resolved keys instead of raw session_ids. New unit tests validate fallback behavior when identifiers are missing and stream isolation semantics.

Changes

Cohort / File(s) Change Summary
Session key resolution in EditPrecisionResponseMiddleware
src/core/services/edit_precision_response_middleware.py
Adds _SESSION_KEY_FIELDS constant and private _resolve_session_key() method to derive a stable session identifier from session_id or alternate fields in context/metadata. Refactors process() and all state access (active_disable_map, _update_stream_tracking, pending_map, _last_stream_ids, hybrid reasoning flags) to use resolved session_key instead of raw session_id. Early-returns with debug log if session_key cannot be resolved.
Session key resolution and cleanup in LoopDetectionMiddleware
src/core/services/response_middleware.py
Adds _resolve_session_key() method to derive stable session identifiers and generate ephemeral aliases for stop_event-based contexts, returning a tuple of (resolved_session_id, ephemeral_key). Adds _cleanup_session_state() method to remove accumulated content and aliases. Updates process() to use resolved_session_id for content accumulation with try/finally cleanup. Enhances loop-detection error reporting with resolved session_id. Imports uuid4 for alias generation. Updates reset_session() to clean up anonymous aliases.
Streaming behavior tests
tests/unit/core/services/test_edit_precision_response_middleware.py
Adds test_streaming_without_session_id_uses_stream_identifier() and test_streaming_without_identifiers_skips_state_changes() tests validating that stream_id is used as fallback pending key and that state changes are skipped when no identifiers are present.
Stream isolation test
tests/unit/core/services/test_response_middleware.py
Adds test_streams_without_session_id_are_isolated() test verifying that independent streaming sessions with distinct stop_event objects maintain isolation and prevent content leakage between streams during accumulation and loop detection.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Middleware
    participant StateStorage
    participant LogicProcessor

    Caller->>Middleware: process(session_id=null, context={stream_id: "s1"}, metadata)
    Middleware->>Middleware: _resolve_session_key(null, {stream_id: "s1"}, metadata)
    Note over Middleware: Scan _SESSION_KEY_FIELDS<br/>session_id empty → check stream_id<br/>found "s1"
    Middleware-->>Middleware: returns "s1"
    Middleware->>StateStorage: access/update with key="s1"
    StateStorage-->>Middleware: state updated
    Middleware->>LogicProcessor: process with resolved key
    LogicProcessor-->>Middleware: result
    Middleware-->>Caller: response

    rect rgb(200, 220, 255)
    Note over Middleware,StateStorage: Fallback scenario: no identifiers found<br/>_resolve_session_key returns None
    Middleware->>Middleware: _resolve_session_key(null, {}, {})
    Note over Middleware: No fields found
    Middleware-->>Middleware: returns None
    Middleware->>Middleware: early return with debug log
    Middleware-->>Caller: continue without state updates
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Key areas requiring attention:
    • Logic correctness of _resolve_session_key() field resolution order and None-handling across both middleware classes
    • State access consistency: verify all state operations in EditPrecisionResponseMiddleware.process() use the resolved session_key
    • Session cleanup semantics in LoopDetectionMiddleware: ensure the try/finally cleanup properly handles ephemeral vs. persistent sessions and prevents state leaks
    • Test coverage completeness: validate that the new tests exercise both fallback paths and edge cases (missing identifiers, stream isolation)
    • Interaction between _resolve_session_key() and _cleanup_session_state() in response_middleware to confirm cleanup targets the correct accumulated content keys

Poem

🐰 A rabbit's ode to session keys:

Through fields we hop, from stream to thread,
Finding identifiers left unsaid,
When session_id hides away,
Stream_id leads us to the fray!
State stays safe, no leaks we fear,
Each session isolated, crystal clear!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix session scoping in response middlewares' directly and clearly summarizes the main change—resolving session scoping issues to prevent data leaks between streaming sessions in middleware implementations.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/fix-cross-message-data-leaks-jlbpv0

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

if is_done:
if alias_value is not None:
self._accumulated_content.pop(alias_value, None)
self._anonymous_session_aliases.pop(alias_id, None)
Copy link

Choose a reason for hiding this comment

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

Bug: Session Cleanup Fails for Metadata-Resolved Sessions

The _cleanup_session_state method fails to clean up accumulated content when sessions are resolved via metadata fields (like stream_id) rather than via stop_event. The cleanup logic checks _anonymous_session_aliases to find which session to clean, but this dictionary is only populated when stop_event is used as the fallback identifier in _resolve_session_key. When a session is resolved earlier via metadata, the mapping is never created, causing accumulated content to persist indefinitely even after the stream completes, resulting in a memory leak.

Fix in Cursor Fix in Web

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +162 to +182
def _cleanup_session_state(
self,
resolved_session_id: str,
ephemeral_key: bool,
stop_event: Any,
) -> None:
if ephemeral_key:
self._accumulated_content.pop(resolved_session_id, None)
return
if stop_event is None:
return
alias_id = id(stop_event)
alias_value = self._anonymous_session_aliases.get(alias_id)
try:
is_done = bool(stop_event.is_set()) # type: ignore[attr-defined]
except AttributeError:
is_done = False
if is_done:
if alias_value is not None:
self._accumulated_content.pop(alias_value, None)
self._anonymous_session_aliases.pop(alias_id, None)

Choose a reason for hiding this comment

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

P1 Badge Leak loop detector state for completed anonymous streams

The new cleanup logic only removes _accumulated_content when an alias was registered for the stop_event, but when the session key comes from metadata (e.g. stream_id) no alias exists. After stop_event.is_set() the method returns without popping the resolved key, so each anonymous stream leaves its accumulated text in memory forever. On a busy server this dictionary will grow unbounded and can also contaminate later streams if an identifier is reused. Consider popping resolved_session_id whenever the stop event finishes, even when alias_value is None.

Useful? React with 👍 / 👎.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/core/services/response_middleware.py (1)

117-160: Code duplication: candidate_fields defined in multiple files.

The candidate_fields tuple (lines 124-132) is duplicated in src/core/services/edit_precision_response_middleware.py (lines 50-58, defined as _SESSION_KEY_FIELDS). Consider extracting this to a shared constant or utility module to maintain consistency.

Example approach:

# In src/core/common/constants.py or similar
SESSION_KEY_FIELDS = (
    "session_id",
    "stream_id",
    "id",
    "request_id",
    "conversation_id",
    "thread_id",
    "message_id",
)

Then import and use in both middleware classes.

src/core/services/edit_precision_response_middleware.py (2)

50-58: Add type hint for class constant.

Per coding guidelines, add a type hint for the class-level constant:

-    _SESSION_KEY_FIELDS = (
+    _SESSION_KEY_FIELDS: tuple[str, ...] = (
         "session_id",
         "stream_id",
         "id",

As per coding guidelines.


192-200: Redundant null check after early return.

Line 192 checks if session_key:, but this is always true at this point since lines 144-148 return early if session_key is None.

Consider removing the redundant check:

-            if session_key:
-                if active_disable_map.get(session_key):
+            if active_disable_map.get(session_key):

Then outdent the entire block from lines 193-277 by one level.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c03159e and 1c57d35.

📒 Files selected for processing (4)
  • src/core/services/edit_precision_response_middleware.py (7 hunks)
  • src/core/services/response_middleware.py (3 hunks)
  • tests/unit/core/services/test_edit_precision_response_middleware.py (1 hunks)
  • tests/unit/core/services/test_response_middleware.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Avoid using emojis in Python code comments or docstrings
Follow PEP 8 and use type hints for all functions
Import order must be: standard library, third-party, then local imports, separated by blank lines
Naming conventions: snake_case for variables and functions; PascalCase for classes
Error handling: use specific exceptions and include meaningful error messages
Prefer f-strings for string formatting

Files:

  • tests/unit/core/services/test_edit_precision_response_middleware.py
  • tests/unit/core/services/test_response_middleware.py
  • src/core/services/edit_precision_response_middleware.py
  • src/core/services/response_middleware.py
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Catch specific exceptions; avoid broad except Exception blocks
If a broad exception must be caught, log with exc_info=True and re-raise a specific custom exception
Use the most specific exception class from src.core.common.exceptions that accurately describes the error
Provide clear, helpful error messages and include relevant details in the details dictionary of custom exceptions

Files:

  • src/core/services/edit_precision_response_middleware.py
  • src/core/services/response_middleware.py
🧬 Code graph analysis (4)
tests/unit/core/services/test_edit_precision_response_middleware.py (3)
src/core/services/application_state_service.py (1)
  • get_setting (154-159)
src/core/services/streaming/middleware_application_processor.py (1)
  • process (38-78)
src/core/ports/streaming.py (1)
  • StreamingContent (11-234)
tests/unit/core/services/test_response_middleware.py (3)
tests/unit/core/test_utilities.py (1)
  • check_for_loops (364-374)
src/core/interfaces/response_processor_interface.py (1)
  • ProcessedResponse (8-30)
src/core/services/response_middleware.py (3)
  • process (20-54)
  • process (60-101)
  • process (184-226)
src/core/services/edit_precision_response_middleware.py (4)
src/core/services/response_middleware.py (1)
  • _resolve_session_key (117-160)
src/core/interfaces/application_state_interface.py (1)
  • set_setting (108-114)
src/core/services/application_state_service.py (1)
  • set_setting (161-165)
src/core/services/app_settings_service.py (1)
  • set_setting (101-118)
src/core/services/response_middleware.py (4)
src/core/services/edit_precision_response_middleware.py (1)
  • _resolve_session_key (92-124)
tests/unit/core/services/test_response_middleware.py (1)
  • is_set (218-219)
src/loop_detection/detector.py (1)
  • check_for_loops (215-260)
src/core/common/exceptions.py (1)
  • LoopDetectionError (154-163)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: test (3.10)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (python)
🔇 Additional comments (10)
tests/unit/core/services/test_edit_precision_response_middleware.py (2)

67-84: LGTM! Good test coverage for stream_id fallback.

This test correctly validates that when session_id is missing, the middleware falls back to stream_id from metadata for session key resolution.


87-105: LGTM! Validates no-op behavior when identifiers are absent.

This test correctly ensures that when no session identifiers can be resolved, the middleware skips state updates entirely, preventing orphaned state entries.

tests/unit/core/services/test_response_middleware.py (1)

205-261: LGTM! Comprehensive test for stream isolation.

This test effectively validates that anonymous streaming sessions remain isolated when using stop_event and stream_id for session resolution. The assertions correctly verify that content from one stream does not leak into another.

src/core/services/response_middleware.py (5)

5-5: LGTM! Import supports anonymous session aliasing.


111-111: LGTM! Proper state tracking for anonymous sessions.


162-182: LGTM! Proper cleanup logic for session state.

The cleanup correctly handles ephemeral keys, stop_event-based sessions, and gracefully handles missing is_set() method.


184-226: LGTM! Process method correctly uses resolved session key.

The refactored process method correctly:

  • Resolves session keys with fallback logic
  • Uses resolved keys for content accumulation
  • Ensures cleanup in finally block
  • Includes resolved session ID in error details

228-234: LGTM! Reset correctly cleans up anonymous aliases.

The updated reset_session method properly removes both accumulated content and any anonymous session aliases, preventing stale aliases from lingering.

src/core/services/edit_precision_response_middleware.py (2)

92-124: LGTM! Session key resolution correctly implemented.

The method properly resolves session keys with appropriate fallback logic for this middleware's needs. The simpler signature (no stop_event, no ephemeral keys) is appropriate since this middleware doesn't need anonymous aliasing.


143-148: LGTM! Early return when session key cannot be resolved.

This correctly prevents state updates when no session identifier can be determined, avoiding orphaned state entries.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant