Repository navigation
fix(gchat): explicit bot identity, media-API downloads, same-space message ids, native forward pagination (#223) - #259
Conversation
…ssage ids, native forward pagination (#223) Ports upstream 32687038 (vercel/chat#830), the Google Chat part of f485255b (vercel/chat#877) and d6343460 (vercel/chat#938); the 2f40a322 alt=media change was already present, only its test is ported. - bot_user_id / GOOGLE_CHAT_BOT_USER_ID is the only identity source; the id is no longer learned from mentions or restored from state. Unknown id fails closed for BOT senders; only this app's mentions are normalized; initialize() warns once when unset. - Attachment bytes come only from the media API by resourceName; the downloadUri fallback and the Google host allowlist are removed; media errors go through _handle_google_chat_error (429 -> AdapterRateLimitError). Python-only: resourceName is validated before it is put in the path. - parse_message_name + _assert_message_in_space guard edit/delete/ add_reaction/remove_reaction; new fetch_message (None on 404). - Forward history is one bounded messages.list page (createTime asc, pageToken = cursor, next_cursor = nextPageToken). Closes #223
|
Warning Review limit reachedNext included review available in 38 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (11)
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. Comment |
…nce, document forward limit=0 - conftest autouse fixture clears GOOGLE_CHAT_BOT_USER_ID so an exported value no longer breaks the 'no bot id configured' tests - bot-id env tests use monkeypatch.setenv; new test pins that an explicit bot_user_id="" does not fall back to the env var - document forward limit=0 -> pageSize=1 (upstream: 0 || 100)
# Conflicts: # tests/conftest.py
|
Merge gate: CI green (test (3.12), test (3.13), Lint & Type Check, CodeQL, Analyze (python), Analyze (actions)); local Codex review (gpt-6-astra, xhigh, --base origin/main) on 2c51cfa: "No actionable regressions were found against the specified merge base. Validation passed: 5,835 tests passed, 24 skipped, and lint checks passed for the affected code and tests."; 1 astra round after merging origin/main (#193/#252/#240/#202; only conflict was tests/conftest.py autouse env fixtures, both kept), on top of the earlier clean verdict on fe3c22b; CodeRabbit rate-limited (no findings). Merging with --admin (Protect Main requires a code-owner approval). |
Summary
Remaining Google Chat hardening from chat@4.38–4.41 (#223):
GoogleChatAdapterConfig.bot_user_id(keyword-only, [4.41/G1] Google Chat: bind webhook JWT verification to configured identity #222 convention) /GOOGLE_CHAT_BOT_USER_ID(config wins,is not None). The id is no longer learned from the first BOT mention, andgchat:botUserIdis never read or written. The learning code and its_pin_taskstate write are gone, so no un-awaited coroutine is left behind.initialize()warns once when the id is unset._is_message_from_selffails closed: with no id, everyBOTsender counts as self._normalize_bot_mentionsrewrites only annotations whoseuserMention.user.name == bot_user_idand keeps the reverse-startIndexorder.fetch_dataexists only whenattachmentDataRef.resourceNameis set, anddownloadUristays inurlfor display only. Errors go through_handle_google_chat_error(error, "fetchAttachmentData"), so a 429 becomesAdapterRateLimitError.rehydrate_attachmentwithoutresourceNamereturns the attachment unchanged._is_trusted_gchat_download_urlis deleted.thread_utils.parse_message_name()uses upstream's pattern withre.fullmatchand returns aGoogleChatMessageName(space_name, message_id)NamedTuple._assert_message_in_spaceruns first inedit_message,delete_message,add_reactionandremove_reaction. Newfetch_message(thread_id, message_id) -> Message | Nonedoes the same check, returnsNoneon 404, and parses the message with the thread Google reports._fetch_messages_forwardnow makes a singlemessages.listcall withpageSize=str(min(max(limit, 1), 1000)),orderBy="createTime asc",pageToken=cursor(only when the cursor is notNone) and the thread filter.next_cursorisnextPageToken.Upstream commits mapped
2f40a322fix(gchat): send alt=media (#801)test_uses_media_download_api_when_attachment_data_ref_is_presentasserts the?alt=mediaURL)32687038fix(gchat): use media api for attachment downloads (#830)_create_attachment,_build_gchat_fetch_data,rehydrate_attachmentf485255bfix(adapters): harden webhook tenant isolation (#877), Google Chat parts onlytypes.pybot_user_id, constructor,initialize,_normalize_bot_mentions,_is_message_from_self,_fetch_messages_forward(webhook body logging was #187)d6343460fix(gchat): reject message ids from another space (#938)thread_utils.parse_message_name,_assert_message_in_space,fetch_messageTests ported (from
packages/adapter-gchat/src/index.test.tsandthread-utils.test.ts)GOOGLE_CHAT_BOT_USER_IDresolution. These are intests/test_gchat_comprehensive.pyand replacetest_restore_bot_user_id_from_stateandtest_does_not_overwrite_existing_bot_user_id.tests/test_google_chat_adapter.py::TestMediaDownload/TestRehydrateAttachment): media API used; fetchData with only attachmentDataRef; downloadUri not fetched when media.download fails; 429 →AdapterRateLimitError; no fetchData with only downloadUri; no rehydrate from a download URL. The "neither resourceName nor downloadUri" test in comprehensive now assertsfetch_data is None.tests/test_gchat_webhook.py): not learned from inbound annotations; mentions of other bots ignored; only this app normalized when another bot is mentioned first; fails closed for BOT senders when the id is unknown. The learn/persist tests intest_gchat_api.pyare flipped to "nogchat:botUserIdstate write" and "a mentioned bot does not become self".tests/test_gchat_api.py::TestMessageSpaceCheck/TestFetchMessage): all four methods reject a foreign space without an API call; the malformed-idit.each(9 cases); the happy path; the three fetchMessage tests. Python-only extras: exact (not prefix) space comparison, threaded and DM thread ids, and non-404 errors going through the handler.test_fetch_forward/test_fetch_forward_with_cursorare replaced). Python-only extra: page-size clamping.tests/test_google_chat_adapter.py::TestParseMessageName): server-assigned name, client-assigned name,rejects %j(14 cases). Python-only sweep: trailing\n, non-ASCII letters and digits, leading or trailing dot, non-strinput.Mutation-checked: disabling each of these makes at least one test fail: the space check, the dot-segment and
?rejection, the fail-closed return, the mention id filter, theresourceName-onlyfetch_data,orderBy, and the 404 →Nonepath.Fidelity
Adapter tests are not fidelity-mapped, so the target report does not change and
scripts/fidelity_target.jsonis not modified:Strict at the pin: 733/733 (730 real + 3 absorbers).
Divergences (3, within budget; rows in
docs/UPSTREAM_SYNC.md, code breadcrumbs, pinned by tests)resourceNamevalidation (issue-mandated). Upstream hands the value to the googleapis client, which encodes it. Python interpolates it into/v1/media/{resourceName}?alt=media, so a non-string or empty value,?,#,%, backslash, anything outside printable ASCII, and./..segments raiseValidationErrorbefore a token is minted.bot_user_idcounts as unset. With no id, nothing is rewritten, whereas upstream'sundefined !== undefinedwould rewrite a BOT annotation that has nouser.name. The "not configured" warning is logged once per adapter instead of on everyinitialize().limit=0(review follow-up).limitresolves withis not None, following the repo-wide port rule, so the upstream clamp sendspageSize=1. Upstream'soptions.limit || 100sends 100.docs/UPSTREAM_SYNC.md: Google Chat is removed from therehydrate_attachmentURL-allowlist row (it no longer fetches any URL), and the three rows above are added.docs/SECURITY.mdgains identity, download and message-id notes.Consumer impact (Google Chat only; none for Slack/Teams)
bot_user_id/GOOGLE_CHAT_BOT_USER_IDto the app'susers/...name. Without it, other bots' messages are ignored and@-mentions of the app are no longer rewritten to@{user_name}, so default mention detection may stop matching.attachmentDataRef.resourceNamehave nofetch_data. Media failures raise the adapter's Google API error (with an HTTPcode) instead ofNetworkError.ValidationError. Ids the adapter returns are already full names.GoogleChatAdapter.fetch_message,thread_utils.parse_message_name.Validation
ruff check / format,
audit_test_quality.py(0 hard failures),--check-docs,--strict(733/733), pytest 5635 passed, 13 skipped, pyrefly 0 errors.Closes #223
Part of #184
Merge gate
Final HEAD:
fe3c22b.Independent review (4 findings; 3 distinct issues)
GOOGLE_CHAT_BOT_USER_IDleaked from the shell into the "no bot id" tests (6 failures with it exported; 2 reviewers reported it). A new autouse fixture intests/conftest.pyclears it for every test.GOOGLE_CHAT_BOT_USER_ID=users/ENVBOT pytest -k 'gchat or google_chat'now gives 532 passed (was 6 failed). The env-resolution tests usemonkeypatch.setenvinstead of saving and restoringos.environby hand.is not Noneprecedence. New testTestConstructorEnvVarResolution::test_empty_configured_bot_user_id_does_not_fall_back_to_env_var: configbot_user_id=""plus the env var set givesNone. It fails under theconfig.bot_user_id or envmutation. The test is cited in the "bot identity edge cases" divergence row.limit=0sendspageSize=1, where upstream'slimit || 100sends 100. The code is unchanged because the repo-wide port rule keepslimit=0(all adapters useis not None, see CHANGELOG "limit=0 no longer silently replaced by defaults"). There is a new divergence row indocs/UPSTREAM_SYNC.mdand a bullet under the CHANGELOG Python-specific section, and the existing(0, "1")case pins the behavior.gpt-6-astra: 2 rounds. Final verdict on
fe3c22b: PASS, no actionable findings.fetchMessagecallsthis.chatApi.spaces.messages.get(app auth). OnlyfetchMessages/fetchChannelMessages/listThreadsuseimpersonatedChatApi || chatApi. Switching to delegated credentials would diverge from upstream and let a message read before acting as the app see what the delegated user can see. To stop the same misreading, the docstring now explains this andtest_reads_with_app_credentials_even_when_impersonation_is_configuredpins it.Bots: CodeRabbit is rate-limited and left no review. There were no gemini comments and no inline review comments.
CI: all green on
fe3c22b: Lint & Type Check (ruff, audit, pin/doc drift, strict fidelity, pyrefly), test 3.12/3.13, CodeQL.Local full validation: ruff check/format OK, audit 0 hard failures,
--check-docsOK, strict fidelity 733/733, pytest 5637 passed / 13 skipped, pyrefly 0 errors.--report-target:missing 282 -> 282 (+0).