fix(gateway/googlechat): handle HTTP endpoint URL events + correct JWT signer email#909
Conversation
…T signer email Two independent bugs prevent the Google Chat adapter from working with the connection mode recommended by docs/google-chat.md: 1. Envelope schema only deserialized the Pub/Sub-wrapped shape, but HTTP endpoint URL connections deliver top-level fields (message, user, space). All real webhooks silently dropped at `envelope.chat is None` with a 200 response. 2. JWT email allow-list expected @gcp-sa-gsuiteaddons.iam.gserviceaccount.com (Workspace Add-ons signer), but Google Chat HTTP webhooks are signed by chat@system.gserviceaccount.com. Setting GOOGLE_CHAT_AUDIENCE per docs returned 401 to every webhook. Together, following the docs produced a 100% non-working bot. This change: - Extends GoogleChatEnvelope with optional top-level fields and adds a fallback branch in the webhook handler; existing wrapped-shape tests continue to pass unchanged. - Renames GOOGLE_CHAT_EMAIL_SUFFIX → GOOGLE_CHAT_SIGNER_EMAIL, changes the value to chat@system.gserviceaccount.com, and tightens the check from `ends_with` to exact equality. - Updates the existing email-claim test to assert the new signer. Verified end-to-end on a production cc-agent deployment (1:1 DM + Space @mention) via Cloudflare Tunnel sidecar; gateway forwards events to OAB and replies arrive in Chat. Closes openabdev#899 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
OpenAB PR ScreeningThis is auto-generated by the OpenAB project-screening flow for context collection and reviewer handoff.
Screening reportdone.GitHub comment: #909 (comment) IntentPR #909 fixes the documented Google Chat HTTP endpoint URL mode, which currently either 401s every webhook or silently 200-drops valid events. FeatFix. Updates Who It ServesDeployers and agent runtime operators using Google Chat. Rewritten PromptFix the Google Chat gateway adapter so HTTP endpoint URL mode works end to end. Support both Pub/Sub wrapped payloads and top-level HTTP payloads, preserve audience verification, require the correct signer email, and add regression tests for both envelope formats and signer validation. Merge PitchThis should move forward. The bug breaks a documented setup path completely, the diff is narrow, and reported validation includes gateway tests plus manual Google Chat E2E checks. Main reviewer concern: env var compatibility around Best-Practice ComparisonOpenClaw and Hermes Agent are not directly applicable; this is adapter-boundary correctness, not scheduling or durable execution. The relevant principle is avoiding silent drops at external delivery boundaries. Implementation Options
Comparison Table
RecommendationAdvance the balanced path. Before merge, review signer compatibility, old env var behavior, and regression coverage for both payload shapes. |
|
LGTM ✅ — Both bugs fixed correctly; unit test coverage added for the new code path. What This PR DoesFixes two independent bugs that made Google Chat HTTP endpoint URL mode completely non-functional: (1) envelope parsing only recognized Pub/Sub wrapped format, silently dropping all HTTP endpoint URL webhooks; (2) JWT email allow-list referenced the wrong signer, rejecting all valid requests. How It Works
四問 (Four Questions)
Findings
Baseline Check
Reviewers
|
|
Hi @sebastian-hsu — thanks for digging into this. I reproduced PR #909 end-to-end in a separate Google Workspace, and want to flag findings + propose a small change before this merges. TL;DR: the envelope fix is solid; the signer change as written breaks every existing deployment that PR #718 was built against. There are (at least) two legitimate Chat App configurations in the wild that use different signer/envelope combinations, so the signer check needs to be an env-var allow-list, not a single hardcoded value. FindingsTwo distinct, both-official Chat App configurations produce different signer + envelope combinations:
Config A is what PR #718 was built and tested against — the original Config B is what PR #909 targets. PR #909 changes the check from a suffix match ( Evidence
ProposalReplace the hardcoded const with an env-var allow-list that ships with sane defaults covering both configurations: const DEFAULT_SIGNERS: &str = "chat@system.gserviceaccount.com,\
*@gcp-sa-gsuiteaddons.iam.gserviceaccount.com";
// GOOGLE_CHAT_SIGNERS: comma-separated list, supports exact match or `*@suffix` glob.
fn signer_allowlist() -> Vec<String> {
std::env::var("GOOGLE_CHAT_SIGNERS")
.unwrap_or_else(|_| DEFAULT_SIGNERS.into())
.split(',')
.map(|s| s.trim().to_owned())
.filter(|s| !s.is_empty())
.collect()
}Both known-good signers ship in the default; future Google additions can be configured without a code change. Asks
Happy to push the allow-list change as a follow-up commit on this PR or as a separate PR — whichever you prefer. |
What problem does this solve?
Following
docs/google-chat.mdend-to-end produces a 100% non-workingGoogle Chat bot. Two independent bugs in
gateway/src/adapters/googlechat.rsmake the documented HTTP endpoint URL connection mode unusable:
Envelope schema mismatch —
GoogleChatEnvelopeonly deserializes achat-wrapped payload (Pub/Sub style), but HTTP endpoint URL connectionsdeliver a top-level shape (
message,user,space,thread,common). All real webhooks silently drop atenvelope.chat is Nonewith a 200 response, so Google considers them "delivered" but no event
reaches the agent.
JWT email allow-list incorrect —
GOOGLE_CHAT_EMAIL_SUFFIXrequires@gcp-sa-gsuiteaddons.iam.gserviceaccount.com(Workspace Add-onssigner), but Google Chat HTTP webhooks are signed as
chat@system.gserviceaccount.com. SettingGOOGLE_CHAT_AUDIENCEperdocs returns 401 to every webhook.
Together, a fresh setup yields "OpenAB not responding" (audience set) or
zero replies (audience unset).
Closes #899
Discord Discussion URL: N/A — bug discovered during personal Google
Workspace setup; reproduction details and captured webhook body are in
issue #899.
At a Glance
Prior Art & Industry Research
OpenClaw: No Google Chat adapter; not applicable.
Hermes Agent: No Google Chat adapter; not applicable.
Other references:
https://developers.google.com/workspace/chat/format-events
Confirms top-level shape (
message,user,space,thread,common) andemailclaim ofchat@system.gserviceaccount.com.schema, likely assuming Pub/Sub deployment.
Proposed Solution
Bug 1 — envelope schema:
GoogleChatEnvelopewith optional top-levelmessage,user,spacefields.webhook(), prefer the wrapped path; fall back to top-level whenenvelope.chatisNone.remain unchanged and still pass.
Bug 2 — JWT email allow-list:
GOOGLE_CHAT_EMAIL_SUFFIX→GOOGLE_CHAT_SIGNER_EMAILand setthe value to
chat@system.gserviceaccount.com(Google Chat's actualsigner for HTTP endpoint URL mode).
ends_with(suffix match) to exact equality.email_claim_accepts_*test to assert the newsigner.
Why this approach?
Schema fallback rather than format-detection by an env var:
of the
chatfield), so serde +Optiongives us a zero-cost branchwith no new config surface.
For Bug 2, narrowing the email check rather than removing JWT verification
preserves the security posture (proves the request came from Google Chat
specifically, not just any Google service) while matching reality.
Alternatives Considered
GOOGLE_CHAT_EVENT_FORMAT=wrapped|toplevelenv var — explicitbut adds config burden; users would need to know which mode their
Chat App uses. The schema fallback is zero-config.
"insecure (local development only)". Bad default; anyone who learns
the webhook URL can inject events.
@system.gserviceaccount.com— broader than needed,could let other Google internal services spoof Chat events. Narrow
to
chat@system.gserviceaccount.com.deployment model on every user; HTTP endpoint URL is what
docs/google-chat.mdcurrently recommends and what current usersfollow.
Validation
cargo check --releasepasses (fresh build onrust:1-bookworm)cargo test --release --package openab-gateway— 170 passed, 0 failed(existing wrapped-shape tests untouched; updated email-claim test asserts new signer)
cc-agentpodgateway: googlechat webhook received → googlechat → gateway → OAB spawn → cc-agent reply via Chat API→ reply visible in Chat client🤖 Generated with Claude Code
https://discord.com/channels/1491295327620169908/1491365158868619404/1507822388883230721