Skip to content

feat(nodes,vscode): add Gmail tool node + Google user-OAuth broker (RR-1055, RR-1142)#1334

Open
joshuadarron wants to merge 10 commits into
developfrom
feat/RR-1055-tool-gmail-user-oauth
Open

feat(nodes,vscode): add Gmail tool node + Google user-OAuth broker (RR-1055, RR-1142)#1334
joshuadarron wants to merge 10 commits into
developfrom
feat/RR-1055-tool-gmail-user-oauth

Conversation

@joshuadarron

@joshuadarron joshuadarron commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add tool_gmail node exposing 61 Gmail v1 API operations as agent tools: read/search/organize mail, labels, drafts, filters, send (incl. HTML + attachments + in-thread replies), settings (IMAP/POP/vacation/forwarding/delegates/send-as/S-MIME), push notifications, and gated hard-delete.
  • Add settings / settings_sharing access tiers and allowHardDelete gate to the shared Google scope resolver.
  • Wire Google user-OAuth via the RocketRide-hosted broker in the VS Code extension: system-browser flow, deep-link callback, pending token delivery to open node-config panels.
  • Fix LoginWithGoogleButton to pass an explicit scope param to the OAuth broker for extended Gmail tiers (send, settings, settings_sharing, full) so the resulting token actually covers the required Google scopes.
  • Fix agent_langchain JSON envelope parser to tolerate conversational preamble text the LLM sometimes outputs before the JSON object (scan for { before raw_decode).

Consolidates and supersedes PR #1333 (RR-1055) and PR #1192 (RR-1142).

Test plan

  • Gmail tool node: 72 unit tests pass (pytest nodes/test/tool_gmail/test_gmail.py)
  • agent_langchain parser: 12 unit tests pass (pytest nodes/test/test_agent_langchain_parser.py)
  • Manual: set Gmail node to settings tier → Login with Google → confirm broker OAuth URL includes gmail.settings.basic scope → run a filter-create pipeline end-to-end without "403 scope" or "unparseable output" errors

Closes RR-1055
Closes RR-1142

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added/expanded Gmail tool integration, including additional access tiers (including a full tier) and stronger permanent delete/write gating.
    • Enhanced Google OAuth to support an external-browser deep-link return flow, with host-driven token handoff into the web UI.
  • Bug Fixes

    • Improved LangChain tool-call parsing to tolerate non-JSON preambles and return deterministic errors when output can’t be parsed.
    • Improved social login button text rendering when internationalization isn’t fully initialized.
  • Tests

    • Added unit and integration coverage for Gmail operations and the updated OAuth/deep-link flows.

joshuadarron and others added 9 commits June 5, 2026 11:00
… (RR-1055)

Extends the GMAIL spec so the Gmail node can support permanent delete: a
'full' tier granting https://mail.google.com/ and the allowHardDelete flag
that gates it. can_write stays scope-derived. Prereq for tool_gmail.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Full Gmail v1 surface as agent tools, modeled on tool_github and gated by the
shared GMAIL resolver:

- Reads/sync: message_list/get/search, thread_get/list, label_list,
  draft_list/get, attachment_get, history_list.
- Organize (write tier): message_modify, message_batch_modify, label_apply/
  remove/create/update/delete. Read-state via the UNREAD label.
- Write: message_send (reply-in-thread sets In-Reply-To/References),
  message_trash/untrash, draft_create/update/send/delete. Send requires the
  send or full tier.
- Permanent delete (gated): message_delete, messages_batchDelete — require
  allowHardDelete + the full tier; explicit id lists only, capped at 1000.

Auth branches on google.authType (service account w/ domain delegation, or
user OAuth). Outputs are cleaned shapes, not raw API JSON. Adds a Google SDK
mock for ROCKETRIDE_MOCK and unit tests covering each function, gating,
reply threading, batch caps, and the services.json field contract.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-1055)

The Google/Microsoft/Slack login button widgets rendered the raw key
(e.g. ADDSOURCE.FORMSTEP.LOGINWITHGOOGLEBUTTON) — uppercased by MUI and
overflowing — when the addSource.* namespace isn't loaded (as in the VS Code
config panel, where the Gmail node first surfaced it). Add a defaultValue to
each t() so the label is correct regardless of i18n state, and set
textTransform:'none' so it renders in sentence case without clipping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nset

defaultValue is ignored because no i18next instance is initialized in the
webview hosts — t() returns the raw key. Detect the key echo and substitute a
literal ('Login with Google'/Microsoft/Slack, 'Authenticated') so the buttons
never render a raw i18n key. Supersedes the prior defaultValue attempt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Make the shared "Login with Google" widget functional in the VS Code
extension. OAuth is brokered by a RocketRide-hosted function (separate
infra), so the engine holds no client secret and self-hosters need no
Google app of their own.

- shared-ui: OAUTH_ROOT_URL build-time constant (config/oauth.ts);
  thread oauthReturnUrl / onOpenExternal / pendingOAuthTokens through the
  flow context; button opens the system browser instead of an iframe
  (Google blocks framing); useOAuthCallbacks token-merge core reused for
  both the URL and message return paths.
- vscode: send oauthReturnUrl in project:load; project:openExternal opens
  the broker in the system browser and arms a one-shot token callback;
  CloudAuthProvider routes the vscode://.../auth/google deep link;
  ProjectWebview delivers tokens to the open node-config panel.
- rsbuild: define REACT_APP_OAUTH_ROOT_URL for the webview bundle.

Deferred: gmail_client.py refresh mapping (lives on RR-1055 branch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tended tiers

- Expand tool_gmail from 26 to 61 tools: add filters, settings (IMAP/POP/
  vacation/forwarding/delegates/send-as/S/MIME), push notifications, HTML
  send with attachments, thread-level convenience ops, check_connection.
- Add settings / settings_sharing access tiers and allowHardDelete gate to
  the shared Google scope resolver.
- Add broker-aware token refresh path in gmail_client.py.
- Add check_connection tool so the agent can diagnose OAuth scope gaps before
  hitting a 403 at API call time.
- Fix LoginWithGoogleButton to pass an explicit scope param to the OAuth broker
  for extended Gmail tiers (send, settings, settings_sharing, full), ensuring
  the resulting token covers the required Google scopes.
- 72 unit tests (was 20).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… JSON envelope

The LLM sometimes outputs conversational text before the required JSON object
(e.g. "I'd be happy to help! {\"type\":\"tool_call\"...}"). The previous
raw_decode call started from index 0 and failed immediately on non-JSON text.

Fix: scan for the first '{' and pass that position as the idx argument to
raw_decode, so preamble text and markdown code fences are skipped rather
than causing a parse failure. Add 12 unit tests covering clean input, preamble
text, markdown fences, trailing text, and all failure paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added docs Documentation module:nodes Python pipeline nodes module:vscode VS Code extension module:ui Chat UI and Dropper UI labels Jun 22, 2026
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 86d7ee2e-fa95-46fb-9206-b25833d781d9

📥 Commits

Reviewing files that changed from the base of the PR and between 2747a90 and 71e5ff5.

📒 Files selected for processing (3)
  • nodes/src/nodes/core/google_access.py
  • nodes/src/nodes/tool_gmail/services.json
  • nodes/test/test_agent_langchain_parser.py

📝 Walkthrough

Walkthrough

Adds a new tool_gmail Python node exposing ~60 Gmail API v1 tool functions with tiered access enforcement, a supporting gmail_client helper layer, expanded Google access scopes, and a full test suite. Separately wires a VS Code Google OAuth deep-link round-trip through CloudAuthProvider and threads four OAuth props down the entire shared-ui component tree to NodeConfigPanel and social-login buttons. Also fixes the LangChain envelope parser to tolerate non-JSON preamble.

Changes

VSCode Google OAuth Deep-Link + Shared-UI Prop Threading

Layer / File(s) Summary
Protocol types and OAuth config constant
apps/vscode/src/providers/views/types.ts, packages/shared-ui/src/config/oauth.ts, apps/vscode/rsbuild.config.mjs, packages/shared-ui/rslib.config.ts
Adds project:oauthTokens host-to-webview and project:openExternal webview-to-host message variants; creates OAUTH_ROOT_URL from REACT_APP_OAUTH_ROOT_URL; registers the env var in both bundler define blocks.
VS Code extension OAuth deep-link handler
apps/vscode/src/auth/CloudAuthProvider.ts, apps/vscode/src/providers/ProjectProvider.ts
CloudAuthProvider gains pendingGoogleOAuth one-shot slot, setPendingGoogleOAuth, and a /auth/google URI handler. ProjectProvider adds oauthReturnUrl to project:load, and handles project:openExternal by registering the pending callback, opening the URL, then forwarding project:oauthTokens back to the webview.
ProjectWebview bridge
apps/vscode/src/providers/views/Project/ProjectWebview.tsx
Adds oauthReturnUrl/pendingOAuthTokens React state, extends handleMessage for both new message types, adds handleOpenExternal and clearPendingOAuthTokens outbound callbacks, and updates <ProjectView> prop wiring.
Shared-UI OAuth prop propagation
packages/shared-ui/src/modules/project/ProjectView.tsx, packages/shared-ui/src/components/canvas/Canvas.tsx, packages/shared-ui/src/components/canvas/components/FlowContainer.tsx, packages/shared-ui/src/components/canvas/context/FlowProvider.tsx, packages/shared-ui/src/components/canvas/context/FlowProjectContext.tsx
Threads oauthReturnUrl, onOpenExternal, pendingOAuthTokens, and clearPendingOAuthTokens through IProjectViewPropsIFlowPropsIFlowContainerPropsIFlowProviderPropsIFlowProjectContext, including the memoized context value and useMemo dependency list.
OAuth token application and button updates
packages/shared-ui/src/components/canvas/components/panels/node-config/NodeConfigPanel.tsx, packages/shared-ui/src/components/canvas/components/panels/node-config/useOAuthCallbacks.ts, packages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithGoogleButton.tsx, packages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWith*.tsx
useOAuthCallbacks extracts applyGoogleTokens as a reusable data-transform callback. NodeConfigPanel adds a useEffect that parses the state node_id, applies tokens, persists, and clears pending state. LoginWithGoogleButton switches to oauthReturnUrl/onOpenExternal and adds Gmail-tier scope selection. All three social buttons adopt an i18n fallback label() helper.

Gmail Tool Node (Python backend)

Layer / File(s) Summary
Google access spec expansion and service config
nodes/src/nodes/core/google_access.py, nodes/src/nodes/tool_gmail/services.json, nodes/src/nodes/tool_gmail/requirements.txt, nodes/src/nodes/tool_gmail/README.md, nodes/src/nodes/tool_gmail/__init__.py
Expands GMAIL AccessSpec with settings/settings_sharing/full tiers and allowHardDelete flag; registers the tool_gmail:// protocol with a gmail.access enum config; adds Google API Python client dependencies and tool documentation.
Gmail API client helper
nodes/src/nodes/tool_gmail/gmail_client.py
Provides build_service() supporting user-OAuth and service-account credential paths (including domain-wide delegation and broker-token refresh); execute() with exponential backoff; build_raw_message()/build_html_message() MIME assemblers; and clean_* response normalizer functions for all Gmail entity types.
IGlobal lifecycle
nodes/src/nodes/tool_gmail/IGlobal.py
beginGlobal resolves access tier and authType, then calls build_service(). validateConfig warns on misconfiguration and verifies granted scopes vs. requested tier for user-OAuth. endGlobal clears service and access.
IInstance tool surface (~60 tool functions)
nodes/src/nodes/tool_gmail/IInstance.py
Exposes the full Gmail v1 surface: message/search/modify/batch-modify, label CRUD, thread ops, draft lifecycle, message_send with reply-thread header construction, irreversible delete (full tier), convenience wrappers (archive/star/mark-read/get_body), and settings tools (filters, watch, send-as, IMAP/POP, vacation, forwarding, delegation, S/MIME).
Mock SDK, unit tests, and integration tests
nodes/test/mocks/google/oauth2/..., nodes/test/mocks/googleapiclient/..., nodes/test/tool_gmail/test_gmail.py, nodes/test/tool_gmail/test_gmail_integration.py, nodes/test/core/test_google_access.py
Adds google.oauth2 and googleapiclient mock packages with canned responses. Unit tests cover all IInstance operations, access gating, MIME payloads, and services.json contract. Integration tests run against real Gmail API when env vars are present. Updates test_google_access.py for new flags/full tier.

LangChain Envelope Parser Fix

Layer / File(s) Summary
Parser fix and unit test suite
nodes/src/nodes/agent_langchain/langchain.py, nodes/test/test_agent_langchain_parser.py
_parse_tool_call_envelope now uses str.find('{') + raw_decode to tolerate non-JSON preamble and markdown fences; returns None when no brace is found. The retry loop returns a fixed error AIMessage after 3 failures. New test suite validates all parse paths including preamble, fenced blocks, trailing text, and negative cases.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant LoginWithGoogleButton
  participant ProjectWebview
  participant ProjectProvider
  participant CloudAuthProvider
  participant SystemBrowser
  User->>LoginWithGoogleButton: Click "Login with Google"
  LoginWithGoogleButton->>ProjectWebview: postMessage project:openExternal { url }
  ProjectWebview->>ProjectProvider: project:openExternal { url }
  ProjectProvider->>CloudAuthProvider: setPendingGoogleOAuth(callback)
  ProjectProvider->>SystemBrowser: vscode.env.openExternal(url)
  SystemBrowser->>CloudAuthProvider: deep-link /auth/google?tokens=…&state=…
  CloudAuthProvider->>ProjectProvider: callback(tokens, state)
  ProjectProvider->>ProjectWebview: postMessage project:oauthTokens { tokens, state }
  ProjectWebview->>NodeConfigPanel: pendingOAuthTokens updated via context
  NodeConfigPanel->>NodeConfigPanel: applyGoogleTokens → persistOAuthTokensAndSave → clearPendingOAuthTokens
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Possibly related PRs

  • rocketride-org/rocketride-server#1074: Updates nodes/src/nodes/core/google_access.py's GMAIL access spec (tiers/scopes and allowHardDelete flag), which is directly tied to the Gmail tool node's access-tier gating and scope requirements.

Suggested reviewers

  • jmaionchi
  • stepmikhaylov
  • Rod-Christensen

Poem

🐇 Hop, hop — a new Gmail node appears!
Sixty tools to send and sort, no fears.
Deep-links bounce through VS Code's door,
OAuth tokens land on the right floor.
The parser skips preamble with glee,
Finding JSON where it ought to be! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and clearly summarizes the main changes: adding a Gmail tool node and Google user-OAuth broker integration to both the nodes and VS Code extension, which are the primary objectives of this changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/RR-1055-tool-gmail-user-oauth

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
🤖 Internal: Discord sync marker

Auto-managed by the Discord notification workflow. Stores the linked Discord message ID. Do not edit or delete.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/shared-ui/src/components/canvas/components/panels/node-config/useOAuthCallbacks.ts (1)

45-54: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Parse failure continues with invalid token data.

If JSON.parse(tokensParam) throws, the function continues building fullTokenObject with undefined values (since tokens remains {}), resulting in a userTokenJson like {"access_token":undefined,...} being stored. This could corrupt the node config.

Return the original formData on parse error to prevent storing invalid data.

🐛 Proposed fix
 		try {
 			if (stateParam) {
 				parsedState = JSON.parse(stateParam);
 				service = JSON.parse((parsedState?.service as string) ?? '{}');
 				authType = (parsedState?.type as string) || '';
 			}
 			tokens = JSON.parse(tokensParam);
 		} catch (error) {
 			console.error('Error parsing Google OAuth callback data:', error);
+			return formData;
 		}
🤖 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
`@packages/shared-ui/src/components/canvas/components/panels/node-config/useOAuthCallbacks.ts`
around lines 45 - 54, When JSON.parse(tokensParam) throws an error in the
try-catch block, the function continues executing with tokens remaining as an
empty object, causing the fullTokenObject to be populated with undefined values
that get stored in the node config. Modify the catch block to return the
original formData immediately when a parse error occurs, preventing the function
from proceeding with invalid data. This ensures that if either stateParam or
tokensParam cannot be parsed, the original formData is returned without creating
a corrupted fullTokenObject with undefined token fields.
🤖 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 `@apps/vscode/src/auth/CloudAuthProvider.ts`:
- Around line 46-47: Replace the single global `pendingGoogleOAuth` callback
slot in the CloudAuthProvider class with a state-keyed map to handle multiple
concurrent OAuth attempts safely. Instead of storing one callback function as
`pendingGoogleOAuth`, create a Map or object that keys callbacks by their OAuth
state parameter. Update the callback assignment logic (in the area around lines
106-131) to store the callback keyed by state, and update the callback
invocation to look up and retrieve the correct callback using the returned state
parameter. This ensures each OAuth flow maintains its own callback and prevents
token misrouting when multiple login attempts happen concurrently.

In `@apps/vscode/src/providers/ProjectProvider.ts`:
- Around line 480-485: The code sets a pending OAuth callback via
CloudAuthProvider.getInstance().setPendingGoogleOAuth() but does not handle
failures from the vscode.env.openExternal call. If openExternal throws an error,
the registered callback remains pending and could be triggered by an unrelated
deep-link response later. Wrap the openExternal call in a try-catch block, and
in the catch handler, clear the pending OAuth callback by calling
CloudAuthProvider.getInstance().setPendingGoogleOAuth with a null or cleared
state to prevent the stale callback from consuming future responses. Also handle
the error appropriately such as logging it or notifying the user.
- Around line 372-373: Add documentation in the apps/vscode/docs/ directory
describing the new host↔webview contract changes. Document the new
oauthReturnUrl field and the new project:openExternal and project:oauthTokens
messages, explaining their purpose and how they facilitate OAuth token exchange
between the host and webview. This documentation is required by coding
guidelines whenever extension contract modifications are made.

In `@apps/vscode/src/providers/views/Project/ProjectWebview.tsx`:
- Around line 98-104: In the project:load case handler, unconditionally set
oauthReturnUrl to either the incoming value or a cleared/reset state instead of
only updating when msg.oauthReturnUrl is truthy, which leaves stale values from
previous loads. Additionally, in the same project:load case, add a call to
setPendingOAuthTokens to reset it to an initial cleared state before accepting
new OAuth events, preventing stale token data from persisting across project
loads.

In `@nodes/src/nodes/core/google_access.py`:
- Around line 131-143: The GMAIL AccessSpec has an empty flags tuple which
prevents resolve_google_access(...) from exposing an allowHardDelete gate value,
making hard-delete behavior uncontrollable via an explicit opt-in kill switch.
Populate the flags parameter in the GMAIL AccessSpec (currently set to an empty
tuple) with the appropriate flag(s) that correspond to hard-delete operations to
enable proper safety gating for irreversible delete behavior.

In `@nodes/src/nodes/tool_gmail/gmail_client.py`:
- Around line 100-101: The oauth_server_url is being retrieved directly from the
user token payload (info.get('oauth_server_url')) and subsequently used to make
POST requests carrying the refresh_token, which creates an SSRF vulnerability
where attackers could craft tokens with malicious URLs to leak credentials.
Replace the untrusted oauth_server_url from the token with a trusted broker URL
sourced from server-side configuration or environment variables, and implement
strict allowlisting to validate the scheme and host before making any refresh
token POST requests. This applies to all locations where oauth_server_url is
extracted and used for token refresh operations, including the range around
lines 140-167.

In `@nodes/src/nodes/tool_gmail/IInstance.py`:
- Line 217: The current pattern using args.get('maxResults') or <default> treats
a value of 0 as falsy and silently defaults it instead of validating it. Replace
this pattern with an explicit check that only applies the default when the value
is None (not provided), allowing 0 to be properly validated through the min/max
clamping logic. Apply this fix to all four affected locations: the current line
in the function at line 217, plus the same pattern in thread_list (line 396),
draft_list (line 510), and history_list (line 878), ensuring consistent behavior
across all list endpoints.

In `@nodes/src/nodes/tool_gmail/requirements.txt`:
- Around line 1-4: The requirements.txt file for the tool_gmail node lists
google-auth-oauthlib and related packages without explicit version constraints
for transitive dependencies idna and protobuf that have known vulnerabilities
(idna < 3.15 and protobuf < 4.25.8/5.29.5/6.31.1). Either add explicit version
pins for idna >= 3.15 and protobuf with the safe versions (>= 4.25.8, >= 5.29.5,
or >= 6.31.1 depending on the major version) directly in the requirements.txt
file, or verify that the auto-generated constraints.txt file created when
installing via depends() pins these safe versions.

In `@nodes/test/core/test_google_access.py`:
- Around line 151-153: The test test_gmail_declares_allow_hard_delete_flag()
checks that 'allowHardDelete' is present in GMAIL.flags, but the GMAIL object
definition currently has an empty flags tuple. Locate the GMAIL definition in
the google_access.py file and add 'allowHardDelete' as a string element to its
flags tuple so that the assertion in the test passes.

In `@nodes/test/test_agent_langchain_parser.py`:
- Around line 195-199: Add a new test function called
test_tool_call_non_dict_args_wrapped to improve test coverage for the non-dict
args wrapping behavior implemented in langchain.py around lines 375-376. The
test should parse a tool_call message where the args field contains a non-dict
value (such as a string), and assert that the resulting tool_calls entry has its
args wrapped in an 'input' key dictionary (converting the string value to the
format {'input': value}). This will verify that non-dictionary args are properly
transformed by the parser.

In `@nodes/test/tool_gmail/test_gmail_integration.py`:
- Around line 261-266: The test method test_hard_delete_blocked_at_modify_tier
is calling message_permanent_delete, but the actual hard-delete method exposed
in the tool surface is message_delete. Replace the method call from
message_permanent_delete to message_delete in the inst call to ensure the test
validates the correct permission gate for the actual implemented method.

In
`@packages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithGoogleButton.tsx`:
- Around line 80-111: The useCallback hook in LoginWithGoogleButton.tsx uses the
saveChanges function within the callback but fails to include it in the
dependency array, which can cause stale closures if saveChanges changes. Add
saveChanges to the dependency array alongside the existing dependencies like
formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, oauthReturnUrl,
and onOpenExternal at the end of the useCallback hook definition.

---

Outside diff comments:
In
`@packages/shared-ui/src/components/canvas/components/panels/node-config/useOAuthCallbacks.ts`:
- Around line 45-54: When JSON.parse(tokensParam) throws an error in the
try-catch block, the function continues executing with tokens remaining as an
empty object, causing the fullTokenObject to be populated with undefined values
that get stored in the node config. Modify the catch block to return the
original formData immediately when a parse error occurs, preventing the function
from proceeding with invalid data. This ensures that if either stateParam or
tokensParam cannot be parsed, the original formData is returned without creating
a corrupted fullTokenObject with undefined token fields.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 2e45f8c8-1cb7-495e-b4d0-e0984bcfaf5e

📥 Commits

Reviewing files that changed from the base of the PR and between e1b4da7 and 2747a90.

⛔ Files ignored due to path filters (1)
  • nodes/src/nodes/tool_gmail/gmail.svg is excluded by !**/*.svg
📒 Files selected for processing (36)
  • apps/vscode/rsbuild.config.mjs
  • apps/vscode/src/auth/CloudAuthProvider.ts
  • apps/vscode/src/providers/ProjectProvider.ts
  • apps/vscode/src/providers/views/Project/ProjectWebview.tsx
  • apps/vscode/src/providers/views/types.ts
  • nodes/src/nodes/agent_langchain/langchain.py
  • nodes/src/nodes/core/google_access.py
  • nodes/src/nodes/tool_gmail/IGlobal.py
  • nodes/src/nodes/tool_gmail/IInstance.py
  • nodes/src/nodes/tool_gmail/README.md
  • nodes/src/nodes/tool_gmail/__init__.py
  • nodes/src/nodes/tool_gmail/gmail_client.py
  • nodes/src/nodes/tool_gmail/requirements.txt
  • nodes/src/nodes/tool_gmail/services.json
  • nodes/test/core/test_google_access.py
  • nodes/test/mocks/google/oauth2/__init__.py
  • nodes/test/mocks/google/oauth2/credentials.py
  • nodes/test/mocks/google/oauth2/service_account.py
  • nodes/test/mocks/googleapiclient/__init__.py
  • nodes/test/mocks/googleapiclient/discovery.py
  • nodes/test/test_agent_langchain_parser.py
  • nodes/test/tool_gmail/__init__.py
  • nodes/test/tool_gmail/test_gmail.py
  • nodes/test/tool_gmail/test_gmail_integration.py
  • packages/shared-ui/rslib.config.ts
  • packages/shared-ui/src/components/canvas/Canvas.tsx
  • packages/shared-ui/src/components/canvas/components/FlowContainer.tsx
  • packages/shared-ui/src/components/canvas/components/panels/node-config/NodeConfigPanel.tsx
  • packages/shared-ui/src/components/canvas/components/panels/node-config/useOAuthCallbacks.ts
  • packages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithGoogleButton.tsx
  • packages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithMicrosoftButton.tsx
  • packages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithSlackButton.tsx
  • packages/shared-ui/src/components/canvas/context/FlowProjectContext.tsx
  • packages/shared-ui/src/components/canvas/context/FlowProvider.tsx
  • packages/shared-ui/src/config/oauth.ts
  • packages/shared-ui/src/modules/project/ProjectView.tsx

Comment on lines +46 to 47
private pendingGoogleOAuth: ((tokens: string, state: string) => void) | null = null;
private disposables: vscode.Disposable[] = [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Scope pending Google OAuth callbacks by state instead of a single global slot.

The current one-slot callback can be overwritten by another panel’s login before the deep-link returns, which can misroute or drop tokens across concurrent OAuth attempts.

Proposed fix (state-keyed waiter map)
- private pendingGoogleOAuth: ((tokens: string, state: string) => void) | null = null;
+ private pendingGoogleOAuth = new Map<string, (tokens: string, state: string) => void>();

- setPendingGoogleOAuth(callback: (tokens: string, state: string) => void): void {
-   this.pendingGoogleOAuth = callback;
+ setPendingGoogleOAuth(state: string, callback: (tokens: string, state: string) => void): () => void {
+   this.pendingGoogleOAuth.set(state, callback);
+   return () => this.pendingGoogleOAuth.delete(state);
 }

 private handleGoogleOAuth(uri: vscode.Uri): void {
   const params = new URLSearchParams(uri.query);
+  const state = params.get('state') ?? '';
+  if (!state) {
+    vscode.window.showErrorMessage('Google sign-in failed: missing OAuth state.');
+    return;
+  }

-  const callback = this.pendingGoogleOAuth;
-  this.pendingGoogleOAuth = null;
+  const callback = this.pendingGoogleOAuth.get(state);
+  this.pendingGoogleOAuth.delete(state);

   // ... existing error/tokens checks

-  callback(tokens, params.get('state') ?? '');
+  callback(tokens, state);
 }

Also applies to: 106-131

🤖 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 `@apps/vscode/src/auth/CloudAuthProvider.ts` around lines 46 - 47, Replace the
single global `pendingGoogleOAuth` callback slot in the CloudAuthProvider class
with a state-keyed map to handle multiple concurrent OAuth attempts safely.
Instead of storing one callback function as `pendingGoogleOAuth`, create a Map
or object that keys callbacks by their OAuth state parameter. Update the
callback assignment logic (in the area around lines 106-131) to store the
callback keyed by state, and update the callback invocation to look up and
retrieve the correct callback using the returned state parameter. This ensures
each OAuth flow maintains its own callback and prevents token misrouting when
multiple login attempts happen concurrently.

Comment on lines +372 to 373
oauthReturnUrl: `${vscode.env.uriScheme}://rocketride.rocketride/auth/google`,
envKeys,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify extension docs mention newly introduced OAuth bridge contract symbols.
rg -n --iglob 'apps/vscode/docs/**' 'oauthReturnUrl|project:openExternal|project:oauthTokens|/auth/google'

Repository: rocketride-org/rocketride-server

Length of output: 58


🏁 Script executed:

#!/bin/bash
# First, check if apps/vscode/docs exists and list its contents
echo "=== VS Code docs directory contents ==="
fd -t f . apps/vscode/docs/ 2>/dev/null | head -20

echo ""
echo "=== Examining ProjectProvider.ts around lines 372-373 ==="
sed -n '365,380p' apps/vscode/src/providers/ProjectProvider.ts

echo ""
echo "=== Examining ProjectProvider.ts around lines 475-488 ==="
sed -n '470,495p' apps/vscode/src/providers/ProjectProvider.ts

Repository: rocketride-org/rocketride-server

Length of output: 1922


🏁 Script executed:

#!/bin/bash
# Search for these contract terms more broadly in the codebase
echo "=== Searching for oauthReturnUrl in entire apps/vscode ==="
rg -l 'oauthReturnUrl' apps/vscode/

echo ""
echo "=== Searching for project:openExternal in entire apps/vscode ==="
rg -l 'project:openExternal' apps/vscode/

echo ""
echo "=== Searching for project:oauthTokens in entire apps/vscode ==="
rg -l 'project:oauthTokens' apps/vscode/

Repository: rocketride-org/rocketride-server

Length of output: 701


🏁 Script executed:

#!/bin/bash
# Look for any messaging contract or protocol documentation
echo "=== Looking for contract/protocol docs in apps/vscode/docs ==="
if [ -d "apps/vscode/docs" ]; then
  find apps/vscode/docs -type f -name "*.md" -o -name "*.txt" -o -name "*.json" | xargs ls -lh
else
  echo "apps/vscode/docs directory does not exist"
fi

echo ""
echo "=== Searching for 'contract' or 'oauth' mentions in any vscode docs ==="
find apps/vscode -type f \( -name "*.md" -o -name "*.txt" \) -exec grep -l -i 'contract\|oauth' {} \;

Repository: rocketride-org/rocketride-server

Length of output: 444


🏁 Script executed:

#!/bin/bash
# Check the actual content of the documentation files
echo "=== apps/vscode/docs/index.md ==="
cat apps/vscode/docs/index.md

echo ""
echo "=== apps/vscode/docs/installation.md ==="
cat apps/vscode/docs/installation.md

echo ""
echo "=== apps/vscode/docs/usage.md ==="
cat apps/vscode/docs/usage.md

Repository: rocketride-org/rocketride-server

Length of output: 12774


Update VS Code extension-surface documentation for new OAuth bridge contract.

The changes at lines 372-373 and 475-488 introduce new host↔webview contract fields and messages (oauthReturnUrl, project:openExternal, project:oauthTokens). Add documentation in apps/vscode/docs/ describing these contract changes, as required by the coding guidelines for extension contract modifications.

🤖 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 `@apps/vscode/src/providers/ProjectProvider.ts` around lines 372 - 373, Add
documentation in the apps/vscode/docs/ directory describing the new host↔webview
contract changes. Document the new oauthReturnUrl field and the new
project:openExternal and project:oauthTokens messages, explaining their purpose
and how they facilitate OAuth token exchange between the host and webview. This
documentation is required by coding guidelines whenever extension contract
modifications are made.

Source: Coding guidelines

Comment on lines +480 to +485
if (data.url) {
CloudAuthProvider.getInstance().setPendingGoogleOAuth((tokens, state) => {
webview.postMessage({ type: 'project:oauthTokens', tokens, state });
});
await vscode.env.openExternal(vscode.Uri.parse(data.url as string));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle browser-open failures and clear the pending OAuth callback.

If openExternal fails/throws, the registered waiter remains pending and may consume a later unrelated deep-link response.

Proposed hardening
 case 'project:openExternal': {
   if (data.url) {
-    CloudAuthProvider.getInstance().setPendingGoogleOAuth((tokens, state) => {
+    const unregister = CloudAuthProvider.getInstance().setPendingGoogleOAuth(/* state */, (tokens, state) => {
       webview.postMessage({ type: 'project:oauthTokens', tokens, state });
     });
-    await vscode.env.openExternal(vscode.Uri.parse(data.url as string));
+    try {
+      const opened = await vscode.env.openExternal(vscode.Uri.parse(data.url as string));
+      if (!opened) {
+        unregister();
+        vscode.window.showErrorMessage('Google sign-in could not be opened in your browser.');
+      }
+    } catch (error) {
+      unregister();
+      const msg = error instanceof Error ? error.message : String(error);
+      vscode.window.showErrorMessage(`Google sign-in failed to launch: ${msg}`);
+    }
   }
   break;
 }
🤖 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 `@apps/vscode/src/providers/ProjectProvider.ts` around lines 480 - 485, The
code sets a pending OAuth callback via
CloudAuthProvider.getInstance().setPendingGoogleOAuth() but does not handle
failures from the vscode.env.openExternal call. If openExternal throws an error,
the registered callback remains pending and could be triggered by an unrelated
deep-link response later. Wrap the openExternal call in a try-catch block, and
in the catch handler, clear the pending OAuth callback by calling
CloudAuthProvider.getInstance().setPendingGoogleOAuth with a null or cleared
state to prevent the stale callback from consuming future responses. Also handle
the error appropriately such as logging it or notifying the user.

Comment on lines +98 to +104
if (msg.oauthReturnUrl) setOauthReturnUrl(msg.oauthReturnUrl);
setEnvKeys(msg.envKeys ?? []);
break;
}
case 'project:oauthTokens':
setPendingOAuthTokens({ tokens: msg.tokens, state: msg.state });
break;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset OAuth bridge state unconditionally during project:load.

Line 98 only updates oauthReturnUrl when truthy, so an omitted/empty value leaves stale URL state from the previous load. The same load path should also clear any stale pendingOAuthTokens before accepting new OAuth events.

Suggested patch
-				if (msg.oauthReturnUrl) setOauthReturnUrl(msg.oauthReturnUrl);
+				setOauthReturnUrl(msg.oauthReturnUrl);
+				setPendingOAuthTokens(undefined);
 				setEnvKeys(msg.envKeys ?? []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (msg.oauthReturnUrl) setOauthReturnUrl(msg.oauthReturnUrl);
setEnvKeys(msg.envKeys ?? []);
break;
}
case 'project:oauthTokens':
setPendingOAuthTokens({ tokens: msg.tokens, state: msg.state });
break;
setOauthReturnUrl(msg.oauthReturnUrl);
setPendingOAuthTokens(undefined);
setEnvKeys(msg.envKeys ?? []);
break;
}
case 'project:oauthTokens':
setPendingOAuthTokens({ tokens: msg.tokens, state: msg.state });
break;
🤖 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 `@apps/vscode/src/providers/views/Project/ProjectWebview.tsx` around lines 98 -
104, In the project:load case handler, unconditionally set oauthReturnUrl to
either the incoming value or a cleared/reset state instead of only updating when
msg.oauthReturnUrl is truthy, which leaves stale values from previous loads.
Additionally, in the same project:load case, add a call to setPendingOAuthTokens
to reset it to an initial cleared state before accepting new OAuth events,
preventing stale token data from persisting across project loads.

Comment thread nodes/src/nodes/core/google_access.py Outdated
Comment on lines +1 to +4
google-api-python-client
google-auth
google-auth-oauthlib
google-auth-httplib2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Direct node requirements:"
cat nodes/src/nodes/tool_gmail/requirements.txt

echo
echo "Search repo constraints/locks/manifests for protobuf/idna pins:"
fd -i 'constraints*.txt|requirements*.txt|pyproject.toml|poetry.lock|uv.lock|Pipfile.lock' | while read -r f; do
  echo "--- $f"
  rg -n '\bidna\b|\bprotobuf\b' "$f" || true
done

echo
echo "If pins are absent or below patched ranges, update constraints and rerun OSV scan."

Repository: rocketride-org/rocketride-server

Length of output: 5911


🏁 Script executed:

#!/bin/bash
# Check the main nodes requirements for version pins
echo "=== nodes/src/nodes/requirements.txt (full content) ==="
cat nodes/src/nodes/requirements.txt

echo
echo "=== llm_gemini/requirements.txt (to see protobuf pin) ==="
cat nodes/src/nodes/llm_gemini/requirements.txt | head -15

echo
echo "=== milvus/requirements.txt (to see protobuf pin) ==="
cat nodes/src/nodes/milvus/requirements.txt | head -15

echo
echo "=== Check for root-level constraint/lock files ==="
ls -la | grep -E 'constraints|lock|pyproject|Pipfile|poetry|uv.lock' || echo "No constraint files in root"

echo
echo "=== Check if there's a uv.lock or poetry.lock ==="
find . -maxdepth 2 -name '*.lock' -o -name 'uv.lock' 2>/dev/null | head -10

Repository: rocketride-org/rocketride-server

Length of output: 1408


🏁 Script executed:

#!/bin/bash
# Check pyproject.toml for dependency management
echo "=== Root pyproject.toml ==="
cat pyproject.toml

echo
echo "=== Check what google-auth* packages require ==="
# Search repo for any google-auth dependency specifications
rg 'google-auth' nodes/src/nodes/tool_gmail/ -A 2 -B 2

echo
echo "=== Check how nodes are installed (depends function) ==="
rg 'def depends|depends\(' --max-count=20 -B 2 -A 2 | head -50

Repository: rocketride-org/rocketride-server

Length of output: 8041


🏁 Script executed:

#!/bin/bash
# Find the depends() function implementation
echo "=== Looking for depends() function definition ==="
rg 'def depends\(' -B 2 -A 10 | head -60

echo
echo "=== Check if this is a pip-based install or other mechanism ==="
fd -i 'conftest.py|__init__.py' nodes/src/nodes/ | xargs grep -l 'depends' 2>/dev/null || echo "Not found in conftest"

echo
echo "=== Search for depends in main Python files ==="
rg 'from.*depends|import.*depends' --max-count=20

echo
echo "=== Check if google-auth packages pull idna/protobuf ==="
# This would be transitive - let's check the Google client library docs
echo "Searching for any documentation or comments about dependencies:"
rg 'google-auth|google-api-python-client' nodes/src/nodes/tool_gmail/ -B 2 -A 2

Repository: rocketride-org/rocketride-server

Length of output: 15754


🏁 Script executed:

#!/bin/bash
# Check the depends.py implementation for constraint handling
echo "=== depends.py implementation (constraint handling) ==="
head -100 packages/server/engine-lib/rocketlib-python/lib/depends.py

echo
echo "=== Search for constraint files ==="
find . -name '*constraint*' -o -name '*requirements*' -type f | grep -E '\.(txt|txt.lock|in)$' | head -20

echo
echo "=== Check for any constraint file patterns ==="
fd -i 'constraint' --type f | head -20

echo
echo "=== Look for pip constraints usage ==="
rg -i 'pip.*constraint|--constraint' --max-count=10

echo
echo "=== Check depends.py for constraint loading ==="
grep -n 'constraint' packages/server/engine-lib/rocketlib-python/lib/depends.py | head -20

Repository: rocketride-org/rocketride-server

Length of output: 6341


🏁 Script executed:

#!/bin/bash
# Get more of depends.py to understand requirements concatenation
echo "=== depends.py - concatenated requirements logic ==="
sed -n '300,350p' packages/server/engine-lib/rocketlib-python/lib/depends.py

echo
echo "=== depends.py - ensure_constraints full implementation ==="
sed -n '708,750p' packages/server/engine-lib/rocketlib-python/lib/depends.py

echo
echo "=== Check what engine_cache_dir returns ==="
grep -n 'def engine_cache_dir\|engine_cache_dir =' packages/server/engine-lib/rocketlib-python/lib/depends.py | head -5
sed -n '250,290p' packages/server/engine-lib/rocketlib-python/lib/depends.py

echo
echo "=== Look for concatenated requirements ==="
grep -n 'concatenat' packages/server/engine-lib/rocketlib-python/lib/depends.py

Repository: rocketride-org/rocketride-server

Length of output: 4527


🌐 Web query:

google-auth google-auth-oauthlib transitive dependencies idna protobuf versions

💡 Result:

Summary (as of 2026-06-22): 1) Direct dependencies (google-auth-oauthlib -> google-auth) - google-auth-oauthlib declares a dependency on google-auth with an upper bound and exclusions for known-incompatible google-auth versions. In the current upstream setup.py in google-auth-oauthlib, the dependency is: "google-auth>=2.15.0,<3.0.0,!=2.43.0,!=2.44.0,!=2.45.0" (and requests-oauthlib>=0.7.0). [1] 2) google-auth-oauthlib transitive dependency: idna - google-auth-oauthlib’s dependencies pull in requests, and requests depends on idna. For example, a dependency tree shown in a real-world pip resolution conflict indicates idna 3.10 is installed as a transitive dependency of requests (idna v3.10). [2] 3) google-auth transitive dependency: protobuf (indirect, via google-auth users / Google API clients) - google-auth itself is not a protobuf library, but many Google API client libraries that depend on google-auth typically depend on protobuf. A Google developer page listing Python client-lib dependency versions for the Google Ads API shows protobuf versions co-occurring with google-auth-oauthlib versions (e.g., protobuf 4.25.x and 5.26.x/5.27.x/5.28.x are listed alongside google-auth-oauthlib releases). This reflects that protobuf is a dependency in the overall client library stack that uses google-auth/google-auth-oauthlib. [3] 4) Concrete version skew warning involving protobuf at runtime - A reported issue demonstrates protobuf runtime/gencode skew detection (example: gencode version older than the protobuf runtime), with installed protobuf and resulting warning. This is useful context for why you must pay attention to protobuf versions in dependency resolution. [4] What you likely want to do (actionable guidance) - Pin google-auth-oauthlib (and/or google-auth) and use pip’s resolver on a clean environment; the upstream constraints already try to avoid known google-auth incompatibilities. [1] - If you observe protobuf runtime/gencode warnings or grpc/other packages complaining, resolve protobuf version conflicts across the whole graph (especially if grpcio/grpcio-tools are involved). [4] Limitations of this research (important) - The provided authoritative sources above do not give a single authoritative “the transitive dependency list of google-auth and google-auth-oauthlib is exactly: idna==X, protobuf==Y”. Instead, protobuf/idna appear in: (a) the dependency graph of packages that depend on google-auth(/oauthlib) and (b) a dependency conflict report. [3][2] If you tell me your exact versions (pip freeze output, or your target google-cloud/google-ads library versions), I can derive the exact idna/protobuf versions that will be selected under those constraints and explain why.

Citations:


🌐 Web query:

idna protobuf CVE security advisory vulnerable versions 2024 2025 2026

💡 Result:

Key result: I did not find evidence of an “idna protobuf” CVE advisory that combines both libraries into a single vulnerability; instead, I found separate, related DoS/validation issues affecting (1) the Python idna library and (2) the protobuf Python implementation. Below are the concrete, source-backed vulnerable version ranges and advisories I found for 2024–2026. 1) Python idna library (CVE-2024-3651, fixed; incomplete remediation later leads to bypass) - Advisory (idna, pip): GHSA-jjg7-2v4v-x38h. - Affected versions: idna < 3.7. - Patched versions: 3.7. - Description: specially crafted inputs to idna.encode could consume significant resources (DoS), and mitigation is improved input rejection/length handling. [1] 2) Python idna library bypass / continuation (CVE-2026-45409) - Advisory (idna, pip): GHSA-65pc-fj4g-8rjx. - Affected versions: idna < 3.15. [2] - Patched versions: 3.15. [2] - Description: crafted inputs can bypass the earlier CVE-2024-3651 remediation; it abuses valid_contexto before length validation, causing significant resource consumption; mitigation includes upgrading to 3.15 and (as noted in multiple sources) enforcing max domain length of 253 before calling idna.encode. [2][3] 3) Protobuf Python pure-Python backend (CVE-2025-4565) - Advisory: GHSA-8qvm-5x2c-j2w7. - Scope/variant: affects protobuf-python when using the pure-Python backend (PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python or pure-Python wheels); not the default C++ backend. [4] - Severity: High potential DoS due to unbounded recursion hitting Python recursion limit when parsing attacker-controlled crafted protobuf structures. [4] - Affected versions: <4.25.8, <5.29.5, <6.31.1. [4] - Patched versions: 4.25.8, 5.29.5, 6.31.1. [4] Notes / how to use this for dependency remediation - If your stack includes Python idna and protobuf (especially protobuf with pure-Python parsing enabled), the practical action is: upgrade idna to >=3.15 (to address CVE-2026-45409) and upgrade protobuf-python to at least one of the patched lines (4.25.8 / 5.29.5 / 6.31.1 depending on your current major series). [2][4] If you meant a specific CVE that explicitly mentions both idna and protobuf together (or a specific language/ecosystem, e.g., Go “x/net/idna” inside a protobuf service), tell me the CVE/CWE or the exact packages/versions you’re targeting and I’ll re-run the search narrowly for that combined reference.

Citations:


Verify that auto-generated constraints pin patched idna/protobuf versions, or add explicit pins.

The google-auth-oauthlib dependency pulls idna transitively (via requests), and both idna and protobuf have known vulnerabilities:

Since this node installs via depends() without explicit version constraints, confirm the auto-generated constraints.txt pins safe versions or explicitly constrain these packages in the requirements file.

🧰 Tools
🪛 OSV Scanner (2.4.0)

[MEDIUM] 1-1: idna 3.9.0: undefined

(PYSEC-2026-215)


[MEDIUM] 1-1: idna 3.9.0: Internationalized Domain Names in Applications (IDNA): Specially crafted inputs to idna.encode() can bypass CVE-2024-3651 fix

(GHSA-65pc-fj4g-8rjx)


[HIGH] 1-1: protobuf 4.25.9: protobuf affected by a JSON recursion depth bypass

(GHSA-7gcm-g887-7qv7)

🤖 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 `@nodes/src/nodes/tool_gmail/requirements.txt` around lines 1 - 4, The
requirements.txt file for the tool_gmail node lists google-auth-oauthlib and
related packages without explicit version constraints for transitive
dependencies idna and protobuf that have known vulnerabilities (idna < 3.15 and
protobuf < 4.25.8/5.29.5/6.31.1). Either add explicit version pins for idna >=
3.15 and protobuf with the safe versions (>= 4.25.8, >= 5.29.5, or >= 6.31.1
depending on the major version) directly in the requirements.txt file, or verify
that the auto-generated constraints.txt file created when installing via
depends() pins these safe versions.

Source: Linters/SAST tools

Comment thread nodes/test/core/test_google_access.py
Comment on lines +195 to +199
def test_tool_call_args_defaults_to_empty_dict():
raw = '{"type":"tool_call","name":"srv.ping"}'
msg = _parse(raw)
assert msg is not None
assert msg.tool_calls[0]['args'] == {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Optional: Consider adding a test case for non-dict args wrapping.

The parser wraps non-dict args values in an input key (langchain.py lines 375-376), but there's no explicit test for this. While this is pre-existing behavior (not part of the current parser fix), a test case would improve coverage.

Example test:

def test_tool_call_non_dict_args_wrapped():
    raw = '{"type":"tool_call","name":"srv.echo","args":"hello"}'
    msg = _parse(raw)
    assert msg is not None
    assert msg.tool_calls[0]['args'] == {'input': 'hello'}
🤖 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 `@nodes/test/test_agent_langchain_parser.py` around lines 195 - 199, Add a new
test function called test_tool_call_non_dict_args_wrapped to improve test
coverage for the non-dict args wrapping behavior implemented in langchain.py
around lines 375-376. The test should parse a tool_call message where the args
field contains a non-dict value (such as a string), and assert that the
resulting tool_calls entry has its args wrapped in an 'input' key dictionary
(converting the string value to the format {'input': value}). This will verify
that non-dictionary args are properly transformed by the parser.

Comment on lines +261 to +266
def test_hard_delete_blocked_at_modify_tier(self):
"""message_delete should raise without the full tier."""
inst = make_live_inst('modify')
with pytest.raises((ValueError, PermissionError)):
inst.message_permanent_delete({'id': 'fake-id-that-doesnt-exist'})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the implemented hard-delete method name in the gate test.

This test calls message_permanent_delete, while the tool surface used elsewhere in this PR is message_delete; as written, the gate test can fail for the wrong reason and miss the actual permission check.

Proposed fix
 class TestHardDeleteGate:
     def test_hard_delete_blocked_at_modify_tier(self):
         """message_delete should raise without the full tier."""
         inst = make_live_inst('modify')
-        with pytest.raises((ValueError, PermissionError)):
-            inst.message_permanent_delete({'id': 'fake-id-that-doesnt-exist'})
+        with pytest.raises(ga.GoogleAccessError):
+            inst.message_delete({'id': 'fake-id-that-doesnt-exist'})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_hard_delete_blocked_at_modify_tier(self):
"""message_delete should raise without the full tier."""
inst = make_live_inst('modify')
with pytest.raises((ValueError, PermissionError)):
inst.message_permanent_delete({'id': 'fake-id-that-doesnt-exist'})
def test_hard_delete_blocked_at_modify_tier(self):
"""message_delete should raise without the full tier."""
inst = make_live_inst('modify')
with pytest.raises(ga.GoogleAccessError):
inst.message_delete({'id': 'fake-id-that-doesnt-exist'})
🤖 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 `@nodes/test/tool_gmail/test_gmail_integration.py` around lines 261 - 266, The
test method test_hard_delete_blocked_at_modify_tier is calling
message_permanent_delete, but the actual hard-delete method exposed in the tool
surface is message_delete. Replace the method call from message_permanent_delete
to message_delete in the inst call to ensure the test validates the correct
permission gate for the actual implemented method.

Comment on lines +80 to +111
// Tell the broker where to return tokens. Web hosts redirect back to the
// current page; hosts that can't receive a web redirect (VS Code) supply
// a deep link via oauthReturnUrl that they intercept out-of-band.
url.searchParams.set('baseURL', oauthReturnUrl || window.location.href);

// For Gmail tiers beyond the broker's default (modify), pass the required
// scopes explicitly so the broker requests the right Google consent. Keys
// mirror google_access.py GMAIL.scopes. readonly/modify are omitted because
// the broker handles them by default; non-Gmail services whose access field
// uses different values (e.g. 'write') won't match any key here.
const _G = 'https://www.googleapis.com/auth';
const GMAIL_EXTENDED_SCOPES: Record<string, string[]> = {
send: [`${_G}/gmail.modify`, `${_G}/gmail.send`],
settings: [`${_G}/gmail.modify`, `${_G}/gmail.settings.basic`],
settings_sharing: [`${_G}/gmail.modify`, `${_G}/gmail.settings.basic`, `${_G}/gmail.settings.sharing`],
full: ['https://mail.google.com/'],
};
const accessTier = selectedNode?.data?.formData?.parameters?.access as string | undefined;
const tierScopes = accessTier ? GMAIL_EXTENDED_SCOPES[accessTier] : undefined;
if (tierScopes?.length) {
url.searchParams.set('scope', tierScopes.join(' '));
}

const targetUrl = url.toString();
// Use onOpenLink callback for embedded hosts (e.g., VSCode), otherwise do a full-page redirect
if (onOpenLink) onOpenLink(targetUrl);
// VS Code (onOpenExternal) opens the system browser — Google's consent
// screen refuses to render in an embedded iframe — and delivers tokens
// back via pendingOAuthTokens. Web hosts do a full-page redirect.
if (onOpenExternal) onOpenExternal(targetUrl);
else window.location.href = targetUrl;

// eslint-disable-next-line react-hooks/exhaustive-deps, @typescript-eslint/no-explicit-any
}, [(props as any).formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, onOpenLink]);
}, [(props as any).formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, oauthReturnUrl, onOpenExternal]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing saveChanges in useCallback dependency array.

The callback uses saveChanges (line 57) but it's not included in the dependency array (line 111). If saveChanges reference changes, the callback will use a stale closure.

🔧 Proposed fix
-	}, [(props as any).formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, oauthReturnUrl, onOpenExternal]);
+	}, [saveChanges, (props as any).formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, oauthReturnUrl, onOpenExternal]);
🤖 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
`@packages/shared-ui/src/components/canvas/components/rjsf-widgets/social-buttons/LoginWithGoogleButton.tsx`
around lines 80 - 111, The useCallback hook in LoginWithGoogleButton.tsx uses
the saveChanges function within the callback but fails to include it in the
dependency array, which can cause stale closures if saveChanges changes. Add
saveChanges to the dependency array alongside the existing dependencies like
formContext, serviceParam, nodeId, selectedNode, oauth2RootUrl, oauthReturnUrl,
and onOpenExternal at the end of the useCallback hook definition.

1. Restore allowHardDelete flag to GMAIL AccessSpec (google_access.py was
   accidentally changed to flags=() which broke test_gmail_declares_allow_hard_delete_flag
   and silently disabled the hard-delete gate in IInstance.py).
   Also add gmail.allowHardDelete config field to services.json so the spec
   flag is user-configurable.

2. Fix sys.modules stub pollution in test_agent_langchain_parser.py. The
   previous _mod() helper unconditionally overwrote real engine modules
   (rocketlib, ai.common.schema, etc.) in CI where they are already present,
   breaking 30+ unrelated tests. Now stubs are installed only when the module
   is absent (_mod_if_absent), and engine stubs are cleaned up after
   langchain.py is loaded so they cannot shadow real modules for other tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation module:nodes Python pipeline nodes module:ui Chat UI and Dropper UI module:vscode VS Code extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant