Skip to content

fix(security): honor relay-declared sender_type in Google Chat adapter to prevent BOT filter bypass#22107

Closed
memosr wants to merge 1 commit into
NousResearch:mainfrom
memosr:fix/google-chat-relay-bot-filter
Closed

fix(security): honor relay-declared sender_type in Google Chat adapter to prevent BOT filter bypass#22107
memosr wants to merge 1 commit into
NousResearch:mainfrom
memosr:fix/google-chat-relay-bot-filter

Conversation

@memosr

@memosr memosr commented May 8, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

The Google Chat adapter (plugins/platforms/google_chat/adapter.py)
supports three envelope formats — Pub/Sub native, Cloud Function
direct, and a "Format 3" relay/flat shape used by custom Cloud Run
relays. Format 3 hardcodes sender.type = "HUMAN" regardless of the
actual sender:

# Before (vulnerable)
msg: Dict[str, Any] = {
    "name": envelope.get("message_name", "") or "",
    "sender": {
        "name": sender_name_surrogate,
        "email": sender_email,
        "displayName": sender_display,
        "type": "HUMAN",   # ← hardcoded, never "BOT"
    },
    ...
}

This breaks the downstream BOT self-filter at line 1145:

sender_type = sender.get("type") or ""
...
if sender_type == "BOT":
    return  # ← never fires for relay format

Two related issues result:

1. Bot-loop DoS

If the Cloud Run relay forwards every message in a space (including
the bot's own replies — easy to misconfigure), the bot will re-process
its own output indefinitely:

  1. User sends message → relay forwards → bot replies via Chat API
  2. Relay sees the bot's reply in the space, forwards it as a Format 3 envelope
  3. Adapter marks type = "HUMAN" → BOT filter doesn't fire
  4. Bot processes its own reply → generates another reply → infinite loop

The dedup guard at line 1139 doesn't always catch this either, because
relay envelopes can have empty message_name (the relay synthesizes
an event_type rather than carrying a messages/... resource path).

2. Allowlist impersonation (relay-trust-boundary attack)

GOOGLE_CHAT_ALLOWED_USERS is checked at line 1537 against
user_id = sender_email or sender_name. In Format 3, sender_email
comes directly from the envelope:

sender_email = (envelope.get("sender_email") or "").strip()

A caller with write access to the Pub/Sub topic the relay publishes
to (a GCP project member with the right role, or a compromised relay)
can craft an envelope claiming to be any allowlisted user:

{
  "event_type": "MESSAGE",
  "sender_email": "allowlisted@company.com",
  "text": "<malicious command>",
  "space_name": "spaces/TARGET"
}

The adapter accepts the spoofed sender, marks it HUMAN, and the
allowlist check passes. The relay's trust boundary is implicit but
the adapter doesn't enforce it.

Fix

Honor the relay's declared sender_type field (mirrors what Format 1
and Format 2 already do via sender.type). Default to "HUMAN" for
backward compatibility when the field is absent:

sender_type_raw = (envelope.get("sender_type") or "HUMAN")
sender_type = str(sender_type_raw).strip().upper() or "HUMAN"
if sender_type not in {"HUMAN", "BOT"}:
    sender_type = "HUMAN"
msg: Dict[str, Any] = {
    "sender": {
        ...
        "type": sender_type,
    },
    ...
}

A correctly configured relay can now mark its forwarded bot replies
as sender_type: "BOT" and the existing BOT self-filter at line 1145
will reject them. Operators with relays that already mark sender type
need no behavior change; relays that don't are safe by default thanks
to the "HUMAN" fallback.

The relay setup documentation should call out that the relay must
forward sender.type from the original Chat event into the envelope
as sender_type, but that's a docs change rather than a code change.

Type of Change

  • 🔒 Security fix (HIGH — bot-loop DoS + allowlist impersonation)

Checklist

  • Read the Contributing Guide
  • Commit messages follow Conventional Commits
  • Backward compatible — default "HUMAN" matches previous behavior
  • Aligns with how Format 1 and Format 2 already read sender.type
  • Defensive coercion — only accepts "HUMAN" or "BOT", falls back otherwise
  • No behavior change for relays that don't declare sender_type

@alt-glitch alt-glitch added type/security Security vulnerability or hardening P1 High — major feature broken, no workaround comp/plugins Plugin system and bundled plugins comp/gateway Gateway runner, session dispatch, delivery labels May 8, 2026
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Merged via PR #22432. Your commit was cherry-picked onto current main with your authorship preserved in git log; we added 5 regression tests (3 unit on _extract_message_payload covering BOT propagation, HUMAN default, and defensive coercion + 2 end-to-end on _on_pubsub_message that assert the BOT self-filter actually drops relay-forwarded bot replies) and clarified the operator contract in the inline comment. Thanks for the report and fix!

@memosr

memosr commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

Thanks

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

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P1 High — major feature broken, no workaround type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants