feat(messaging): add Google Chat channel#7317
Conversation
Adds a manifest-first Google Chat channel: inbound webhook via the public tunnel, and outbound replies authenticated by a gateway-minted service-account token. The private key is configured through OpenShell's --secret-material-env, so it never appears in argv and never enters the sandbox. Includes the network policy preset, appPrincipal capture flow for personal/standalone accounts, DM allowlist, and tests. Co-authored-by: Carlos Villela <cvillela@nvidia.com> Signed-off-by: Hung Le <hple@nvidia.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds experimental Google Chat support for OpenClaw sandboxes: a new built-in channel manifest, network policy, and provider profile; interactive tunnel/audience enrollment via a dedicated cloudflared webhook proxy; a generic gateway bridge-provider onboarding layer; runtime source patches for outbound auth and trusted-proxy fetch; secret-prompt masking enhancements; lifecycle wiring for add/remove/stop/start and destroy cleanup; and corresponding documentation and tests. ChangesGoogle Chat Channel Cohort
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Operator
participant ChannelsAddCLI
participant AudienceGateHook
participant TunnelRuntime
participant BridgeProvider
participant Gateway
Operator->>ChannelsAddCLI: channels add googlechat
ChannelsAddCLI->>AudienceGateHook: run tunnelAudienceGate hook
AudienceGateHook->>TunnelRuntime: startTunnel (cloudflared + webhook proxy)
TunnelRuntime-->>AudienceGateHook: public tunnel URL
AudienceGateHook-->>ChannelsAddCLI: GOOGLECHAT_AUDIENCE
ChannelsAddCLI->>BridgeProvider: collectMessagingBridgeTokenDefs + ensureMessagingBridgeProfiles
BridgeProvider->>Gateway: provider profile import / refresh configure
ChannelsAddCLI->>Gateway: upsert bridge provider (bestEffort)
Gateway-->>ChannelsAddCLI: registration result
ChannelsAddCLI-->>Operator: channel enabled
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Code Coverage OverviewLanguages: TypeScript TypeScript / code-coverage/pluginThe overall coverage in commit f233693 in the TypeScript / code-coverage/cliThe overall coverage in commit f233693 in the Show a code coverage summary of the most impacted files.
Updated |
PR Review Advisor — InformationalAdvisor assessment: Informational / high confidence Model lanes
Nemotron output stays in workflow artifacts and does not change the assessment above. E2E guidanceAdvisory only. E2E / PR Gate selects and runs jobs independently. Recommended E2E: 1 optional E2E recommendation
This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge. |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts (1)
225-255: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the shared CommonJS/ESM source-rewrite loader infrastructure into one module.
isOpenClawGooglechatFile,errorMessage,fileNameFromModuleUrl,sourceToText,createComposableJsLoader, and theinstallXPatchshape are duplicated near-verbatim between these two files (and reportedly a third,slack-channel-guard.ts, per the in-file comments). OnlypatchSource/the anchor regexes actually differ per patch.
src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts#L225-L255: keep onlypatchGooglechatOutboundAuthSourcehere; delegatecreateComposableJsLoader,sourceToText,fileNameFromModuleUrl,errorMessage, and the file-matcher to a shared helper module (parameterized by channel name/path fragment).src/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.ts#L245-L275: same — keep onlypatchTrustedProxyFetchSourceand delegate the rest to the shared helper.This keeps the two channel-specific files focused on their actual patch logic and avoids a third drifted copy the next time a channel needs this pattern.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts` around lines 225 - 255, Extract the duplicated CommonJS/ESM rewrite infrastructure into a shared helper module, parameterized by channel name/path fragment and preserving the existing installXPatch shape. In src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts lines 225-255, retain only patchGooglechatOutboundAuthSource and delegate createComposableJsLoader, sourceToText, fileNameFromModuleUrl, errorMessage, and isOpenClawGooglechatFile; apply the same change in src/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.ts lines 245-275, retaining only patchTrustedProxyFetchSource and delegating the shared helpers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/manage-sandboxes/enable-channels-during-onboarding.mdx`:
- Line 12: Remove the runtime AgentOnly import from the onboarding documentation
while preserving any build-time AgentOnly directive usage.
In `@src/lib/messaging/channels/googlechat/manifest.ts`:
- Around line 106-117: Update the explanatory comment above credentials in the
manifest to reference the actual messaging bridge provider implementation at
src/lib/onboard/messaging-bridge-provider.ts instead of the nonexistent
googlechat-bridge-provider.ts path; leave the surrounding authentication
description unchanged.
In `@src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts`:
- Around line 264-281: Gate both Module.registerHooks loaders in
src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts:264-281
and
src/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.ts:284-301
on a Node version known to support synchronous hooks, skipping them for Node
22.19.0 through 22.22.2, or raise the repository minimum to a fixed release.
Ensure affected versions never invoke nextLoad for unrelated module loads.
---
Nitpick comments:
In `@src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts`:
- Around line 225-255: Extract the duplicated CommonJS/ESM rewrite
infrastructure into a shared helper module, parameterized by channel name/path
fragment and preserving the existing installXPatch shape. In
src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts lines
225-255, retain only patchGooglechatOutboundAuthSource and delegate
createComposableJsLoader, sourceToText, fileNameFromModuleUrl, errorMessage, and
isOpenClawGooglechatFile; apply the same change in
src/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.ts
lines 245-275, retaining only patchTrustedProxyFetchSource and delegating the
shared helpers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 22d36853-0225-44c0-84fd-db82510b6b78
📒 Files selected for processing (56)
ci/reviewed-npm-lifecycle-allowlist.jsondocs/index.ymldocs/manage-sandboxes/add-channels-after-onboarding.mdxdocs/manage-sandboxes/enable-channels-during-onboarding.mdxdocs/manage-sandboxes/manage-messaging-channels.mdxdocs/manage-sandboxes/messaging-channels.mdxdocs/manage-sandboxes/set-up-google-chat.mdxsrc/lib/actions/sandbox/destroy.tssrc/lib/actions/sandbox/policy-channel-dependencies.tssrc/lib/actions/sandbox/policy-channel.tssrc/lib/messaging-channel-config.test.tssrc/lib/messaging/applier/build/messaging-build-applier.mtssrc/lib/messaging/channels/built-ins.tssrc/lib/messaging/channels/googlechat/hooks/index.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-audience-gate.test.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-audience-gate.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-runtime.test.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-runtime.tssrc/lib/messaging/channels/googlechat/manifest.tssrc/lib/messaging/channels/googlechat/policy/openclaw.yamlsrc/lib/messaging/channels/googlechat/provider-profile/openclaw.yamlsrc/lib/messaging/channels/googlechat/rendered-config-parser.tssrc/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.test.tssrc/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.tssrc/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.test.tssrc/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.tssrc/lib/messaging/channels/googlechat/template-resolver.test.tssrc/lib/messaging/channels/googlechat/template-resolver.tssrc/lib/messaging/channels/manifests.test.tssrc/lib/messaging/channels/metadata.test.tssrc/lib/messaging/channels/rendered-config-parser.tssrc/lib/messaging/channels/template-resolver.tssrc/lib/messaging/diagnostics.test.tssrc/lib/messaging/hooks/builtins.tssrc/lib/messaging/hooks/hook-runner.test.tssrc/lib/messaging/utils.test.tssrc/lib/onboard/messaging-bridge-provider.test.tssrc/lib/onboard/messaging-bridge-provider.tssrc/lib/onboard/messaging-prep.test.tssrc/lib/onboard/messaging-prep.tssrc/lib/onboard/providers.tssrc/lib/sandbox/channels.test.tssrc/lib/state/openclaw-managed-extensions.test.tssrc/lib/tunnel/googlechat-webhook-lifecycle.test.tssrc/lib/tunnel/googlechat-webhook-lifecycle.tssrc/lib/tunnel/googlechat-webhook-proxy.test.tssrc/lib/tunnel/googlechat-webhook-proxy.tssrc/lib/tunnel/services.tstest/channels-add-bridge-lifecycle.test.tstest/channels-add-preset.test.tstest/destroy-cleanup-sandbox-services.test.tstest/e2e/live/channels-stop-start-helpers.tstest/e2e/live/channels-stop-start-progress.tstest/e2e/support/channels-stop-start-progress.test.tstest/messaging-build-applier-integrity.test.tstest/onboard-readiness.test.ts
Signed-off-by: Hung Le <hple@nvidia.com>
Signed-off-by: Hung Le <hple@nvidia.com>
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
Signed-off-by: Hung Le <hple@nvidia.com>
|
🌿 Preview your docs: https://nvidia-preview-pr-7317.docs.buildwithfern.com/nemoclaw |
…plyPreset Signed-off-by: Hung Le <hple@nvidia.com>
… import Signed-off-by: Hung Le <hple@nvidia.com>
Signed-off-by: Hung Le <hple@nvidia.com>
- Group the webhook, appPrincipal, and service-account guidance into aligned left-rail blocks, color the webhook URL, and add blank-line spacing before configuration prompts. - Cap the masked echo for a long pasted secret (the service-account JSON) with an "(and N more characters)" tail and support word-delete in the masked prompt. - Add opt-in re-prompts (maxTokenAttempts) and a Google Chat-owned token-paste hook that rejects an unparseable service-account JSON at the prompt instead of aborting onboarding later at token minting. - Make the tunnel/audience gate the single source of the webhook audience. - Suppress the harmless "profile not found" probe output when registering the bridge provider profile, and surface only an unexpected probe error. - Rename the e2e stop/start heartbeat local to avoid colliding with the merged progress fixture. Signed-off-by: Hung Le <hple@nvidia.com>
…-channel-integration-googlechat
…-channel-integration-googlechat
…ract test Signed-off-by: Hung Le <hple@nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/onboard/messaging-bridge-provider.ts`:
- Around line 375-409: Update buildRefreshMaterial for the
google-service-account-jwt strategy so secretKeys always includes "private_key",
even when profile.secretMaterialKeys is non-empty and omits it; preserve any
other declared secret keys without duplication. Add coverage for a profile
omitting "private_key" and verify it remains secret material rather than argv
material.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 6b05bbe8-689c-4e47-b9ec-410c8ca379f7
📒 Files selected for processing (27)
ci/reviewed-npm-lifecycle-allowlist.jsondocs/index.ymldocs/manage-sandboxes/enable-channels-during-onboarding.mdxsrc/lib/actions/sandbox/destroy.tssrc/lib/actions/sandbox/policy-channel-dependencies.tssrc/lib/actions/sandbox/policy-channel.tssrc/lib/credentials/store.tssrc/lib/messaging/applier/build/messaging-build-applier.mtssrc/lib/messaging/channels/googlechat/hooks/index.tssrc/lib/messaging/channels/googlechat/hooks/service-account-token-paste.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-audience-gate.test.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-audience-gate.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-runtime.tssrc/lib/messaging/channels/googlechat/manifest.tssrc/lib/messaging/channels/googlechat/runtime-contract.test.tssrc/lib/messaging/channels/googlechat/tunnel/lifecycle.test.tssrc/lib/messaging/channels/googlechat/tunnel/lifecycle.tssrc/lib/messaging/channels/googlechat/tunnel/proxy.test.tssrc/lib/messaging/channels/googlechat/tunnel/proxy.tssrc/lib/messaging/channels/metadata.test.tssrc/lib/messaging/hooks/common/config-prompt.tssrc/lib/messaging/hooks/common/token-paste.tssrc/lib/messaging/hooks/hook-runner.test.tssrc/lib/messaging/manifest/types.tssrc/lib/onboard/messaging-bridge-provider.test.tssrc/lib/onboard/messaging-bridge-provider.tstest/channels-add-bridge-lifecycle.test.ts
💤 Files with no reviewable changes (1)
- docs/manage-sandboxes/enable-channels-during-onboarding.mdx
🚧 Files skipped from review as they are similar to previous changes (11)
- src/lib/messaging/hooks/hook-runner.test.ts
- ci/reviewed-npm-lifecycle-allowlist.json
- src/lib/messaging/applier/build/messaging-build-applier.mts
- src/lib/actions/sandbox/policy-channel-dependencies.ts
- docs/index.yml
- src/lib/actions/sandbox/destroy.ts
- src/lib/messaging/channels/googlechat/manifest.ts
- src/lib/messaging/channels/metadata.test.ts
- src/lib/messaging/channels/googlechat/hooks/tunnel-audience-gate.test.ts
- test/channels-add-bridge-lifecycle.test.ts
- src/lib/actions/sandbox/policy-channel.ts
…der-declares secrets Signed-off-by: Hung Le <hple@nvidia.com>
E2E Target Results — ❌ Some tests failedRun: 30082756521
|
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
|
Maintainer security follow-up is pushed at exact GitHub-Verified signed head Small follow-up commits:
Exact-head local validation:
The pinned |
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
|
Addressed advisor blocker PRA-1 in signed, GitHub-Verified commit
Validation:
|
Summary
Adds Google Chat as an experimental OpenClaw messaging channel, served at
/googlechaton the gateway through a dedicated Google-Chat-only cloudflared tunnel and a loopback webhook proxy. Outbound auth is minted gateway-side (OpenShellgoogle-service-account-jwtprovider) and injected by the L7 proxy, so the service-account private key never enters the sandbox. Inbound cert verification and outbound replies both route by hostname through the trusted L7 proxy (the SSRF guard's trusted-proxy modes), so the channel works in the DNS-less proxy-only sandbox — no local DNS resolve, no sentinel IP.Scope: OpenClaw only. Hermes uses a different inbound model (Cloud Pub/Sub pull, no webhook) and a different credential path, so Hermes Google Chat is a planned follow-up rather than part of this PR.
Supersedes #6120: same change, recommitted as a single verified commit with clean history.
Architecture
Google Chat is the first bridge channel: inbound webhooks are verified inside the sandbox, but the outbound bot token is minted on the host and the service-account private key never enters the sandbox. Two JWTs run in opposite directions — Google signs one that the plugin verifies (inbound), and the gateway signs one to mint the outbound token (outbound).
OpenShell Requirement
The Google Chat service-account private key is configured through OpenShell's
--secret-material-env(OpenShell#2178), which keeps it off process arguments. That flag ships in OpenShell v0.0.82+; currentmainpins OpenShell 0.0.85, so the requirement is already satisfied onmain— no prerequisite PR.Related Issue
Parts of #5492
Result
Changes
Channel
googlechatmanifest (inputs, enrollment notes, render) + built-in registration; served on the shared gateway dashboard port at/googlechat(no host forward — on its own dedicated tunnel, see below).nemoclaw tunnelservice wiring.nemoclaw tunnel start) fronts a loopback-only webhook proxy that forwards onlyPOST /googlechat(1 MiB body cap) and returns 404 for every other method or path, so the public URL is a narrow webhook pinhole that never exposes the dashboard or control UI. NemoClaw tears down both the proxy and its cloudflared on skip, remove, and destroy.unexpected add-on principal: <N>(the real value to copy) instead of a numberlessmissing add-on principal binding; inert for Google Workspace accounts. Enrollment notes are rewritten into an always-printed capture guide with the exactlogs --followcommand and the persist-and-rebuild steps.Gateway reliability
gateway.reload.mode=off. OpenClaw rewrites its own config ~60s after boot (auto-enabling default provider plugins); with reactive hot-reload on, that self-write rebuilds the gateway's HTTP route table and drops the Google Chat inbound webhook route, so/googlechatstarts returning 404 and the bot goes silent ~60s after every start. The sandbox'sopenclaw.jsonis sealed at build time (0600 + integrity hash), so disabling reactive reload is safe; NemoClaw still restarts the gateway explicitly on rebuild /gateway restart.Inbound + outbound routing (proxy-only sandbox)
googlechat-trusted-proxy-fetchboot preload: rewrites the plugin's three googleapis fetch sites (inbound cert verify + all outbound sends/edits) to the SSRF guard'strusted_env_proxy/trusted_explicit_proxymodes, so they skip the localgetaddrinfoand route by hostname through the L7 proxy — fixesEAI_AGAINin the DNS-less netns, no sentinel IP. Interim; clean fix is upstream in OpenClaw (like web_fetch #50650).googlechatnetwork-policy preset scoped to the Chat spaces tree, includingPATCHfor streaming message edits.Outbound auth (key out of sandbox)
google-chat-bridgeOpenShell provider profile +googlechat-bridge-provideronboard wiring: import the profile pre-create, runprovider refresh configure --strategy google-service-account-jwtpost-create from the pasted SA JSON — the gateway mints/rotates the token and the L7 proxy injectsAuthorization: Beareron chat.googleapis.com. The private key is supplied through--secret-material-env, so it never reaches argv; the profile authorizes only the Node runtime for the injected bearer.googlechat-outbound-authboot preload: load-time rewrite of the plugin's single token producer to emit the OpenShell credential placeholder (revision-less alias, so it resolves to the latest re-minted token and survives rotation) instead of signing in-process.serviceAccountFileis kept only as a channel-start-gate marker (never delivered or read); keeps theBEGIN PRIVATE KEYsecret scan.Documentation
Type of Change
Quality Gates
gateway.reload.mode=offrendered, the non-existentserviceAccountFilesentinel, and no credentials delivered into the sandbox)Verification
Signed-off-by:line and every commit appears asVerifiedin GitHubpre-commit,commit-msg, andpre-pushhooks passed, ornpm run check:diffpassed when hooks were skipped or unavailablenpm testfor broad runtime/test-harness changes;npm run checkfor repo-wide validation/coverage changesnpm run docsbuilds without warnings (doc changes only)Signed-off-by: Hung Le hple@nvidia.com
Signed-off-by: Carlos Villela cvillela@nvidia.com
Summary by CodeRabbit
New Features
Documentation
Bug Fixes