Skip to content

security(chat): send_direct_chat_response has no ownership check — any user can inject into any chat session #13982

Description

@mrveiss

Found while tracing every process_message_stream entrypoint for #13821 (PR #13981). Pre-existing, not introduced by that PR.

Problem

send_direct_chat_response (autobot-backend/api/chat.py) takes chat_id from the request body and streams a response into that session with no ownership validation.

The sibling endpoint on the same router does validate:

async def send_chat_message_by_id(
    chat_id: str,
    current_user: dict = Depends(get_current_user),
    ...
    ownership: Dict = Depends(validate_chat_ownership),   # SECURITY: Validate ownership
):

send_direct_chat_response has get_current_user but no validate_chat_ownership:

async def send_direct_chat_response(
    current_user: dict = Depends(get_current_user),
    request: Request = None,
    message: str = Body(...),
    chat_id: str = Body(...),
    remember_choice: bool = Body(default=False),
):

Impact

Any authenticated user can post a direct/approval response into any chat session id, including another user's. This endpoint carries approval and denial decisions, so the reachable consequences are worse than message injection:

  • resolving another user's pending command-approval interrupt on their behalf
  • remember_choice=True persists that decision for future turns in a session the caller does not own
  • the injected message drives the workflow, so it can trigger tool execution in someone else's session

Authentication is required, so this is not anonymous — the boundary that is missing is between authenticated users, which is exactly what validate_chat_ownership exists to enforce.

Proposed fix

Add the validate_chat_ownership dependency, matching send_chat_message_by_id. If the omission is deliberate (e.g. an approval flow intended to be answerable by an operator), document why and gate it on an explicit permission rather than leaving it open to every authenticated role.

Acceptance criteria

  • send_direct_chat_response refuses a chat_id the caller does not own, or gates the cross-session case on an explicit permission
  • Test: user A cannot post a direct response into user B's session
  • Test: the owner still can (no regression to the approval flow)
  • Every other process_message_stream entrypoint audited for the same omission, result recorded in the PR

Context

Activity

  1. mrveiss commented on Aug 11, 2026

    @mrveiss
    OwnerAuthor

    Delivered by PR #14009, merged as e4a3ed8e5.

    Verified in the base branch

    $ git log origin/Dev_new_gui --oneline --grep=13982
    e4a3ed8e5 security(chat): require chat ownership on /chat/direct (#13982) (#14009)
    
    $ git show origin/Dev_new_gui:autobot-backend/api/chat.py | grep -c "validate_chat_ownership(chat_id, request)"
    1
    

    Acceptance criteria

    AC status
    the endpoint refuses a chat_id the caller does not own delivered
    test: user A cannot post a direct response into user B's session delivered — behavioural, plus one that the refusal happens before any workflow work
    test: the owner still can (no regression to the approval flow) delivered
    every other process_message_stream entrypoint audited delivered — 15 endpoints in api/chat.py, all ownership-checked

    What the review changed

    Depends(validate_chat_ownership) was the obvious fix and would have been wrong: it resolves chat_id as a path parameter while this endpoint takes it from the body, so it would have validated a different value than the request acts on — a check that passes while guarding nothing. The explicit call matches the pattern the other endpoints in the file already use.

    My stated audit count was also wrong (14, actually 15 — summarize_conversation was already protected). Corrected on the PR.

    Two gaps this surfaced, filed separately

    Closing on the four ACs. Worth reading #14010 before treating this endpoint as protected in a running deployment.

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions