Conversation
…n-pass fix(frontend): harden gateway-tools auth and refine tools UX
Railway Preview Environment
Updated at 2026-02-26T00:30:22.064Z |
…ormalization [3832] chore(api): add canonical non-preview mounts and hide preview aliases
[feat] Add gateway tools
| project_id: Optional[UUID] = None | ||
| if state: | ||
| payload = decode_oauth_state(state, secret_key=env.agenta.crypt_key) | ||
| if payload is None: | ||
| log.warning("OAuth callback: invalid or expired state token") | ||
| else: | ||
| try: | ||
| project_id = UUID(payload["project_id"]) | ||
| except (KeyError, ValueError): | ||
| log.warning("OAuth callback state missing or invalid project_id") | ||
| else: | ||
| log.warning("OAuth callback received without state token") |
There was a problem hiding this comment.
🔴 OAuth callback activates connections without project scoping when HMAC state token is missing or invalid
When the callback_connection endpoint receives a request without a valid state query parameter, project_id remains None and the connection activation proceeds without project scoping — allowing a cross-project privilege escalation.
Root Cause and Impact
The callback handler at api/oss/src/apis/fastapi/tools/router.py:812-823 sets project_id = None and only populates it if a valid HMAC-signed state token is present. When state is missing or invalid (expired, tampered), the code logs a warning but continues to line 829 where it calls activate_connection_by_provider_connection_id(provider_connection_id=connected_account_id, project_id=project_id) with project_id=None.
In the DAO at api/oss/src/dbs/postgres/tools/dao.py:224-225:
if project_id is not None:
stmt = stmt.filter(self.ToolConnectionDBE.project_id == project_id)When project_id is None, the project filter is skipped entirely, so the activation query matches any connection across all projects with the given connected_account_id.
Since this endpoint is unauthenticated (explicitly excluded from auth at api/oss/src/services/auth_service.py:64-65), and since connected_account_id values are exposed in the ToolConnection API responses via the data field, an attacker who obtains a connected_account_id from any source could forge a callback request like GET /preview/tools/connections/callback?connected_account_id=<id> (without state) and activate a pending connection in another project.
Impact: Cross-project privilege escalation — an attacker can activate tool connections they don't own, gaining access to execute tools using another project's OAuth-authenticated integrations.
Prompt for agents
In api/oss/src/apis/fastapi/tools/router.py, the callback_connection method (around lines 812-823) should reject the request when project_id cannot be determined from the state token, instead of proceeding with an unscoped activation.
After the state validation block (lines 812-823), add a check: if project_id is None, return an HTMLResponse with status 400 and an error card explaining that the authorization link has expired or is invalid. For example:
if project_id is None:
log.warning("OAuth callback: rejecting activation — no valid project scope")
return HTMLResponse(
status_code=400,
content=_oauth_card(
success=False,
error="Authorization link expired or invalid. Please try connecting again.",
),
)
This should be inserted BEFORE the activation block at line 826. This ensures that connections can only be activated when the callback carries a valid, HMAC-verified project_id from the signed state token.
Was this helpful? React with 👍 or 👎 to provide feedback.
New version v0.87.0 in