Skip to content

[release] v0.87.0#3821

Merged
junaway merged 48 commits intomainfrom
release/v0.87.0
Feb 26, 2026
Merged

[release] v0.87.0#3821
junaway merged 48 commits intomainfrom
release/v0.87.0

Conversation

@github-actions
Copy link
Contributor

@github-actions github-actions bot commented Feb 25, 2026

New version v0.87.0 in

  • (web)
  • web/oss
  • web/ee
  • sdk
  • api
  • services

Open with Devin

@github-actions
Copy link
Contributor Author

github-actions bot commented Feb 25, 2026

Railway Preview Environment

Image tag pr-3821-8185a62
Status Failed
Logs View workflow run

Updated at 2026-02-26T00:30:22.064Z

@jp-agenta jp-agenta changed the base branch from release/v0.86.8 to main February 25, 2026 12:57
jp-agenta and others added 2 commits February 25, 2026 14:56
…ormalization

[3832] chore(api): add canonical non-preview mounts and hide preview aliases
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:S This PR changes 10-29 lines, ignoring generated files. labels Feb 25, 2026
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Feb 25, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

@junaway junaway enabled auto-merge February 26, 2026 00:28
@junaway junaway merged commit 3e93d47 into main Feb 26, 2026
10 of 11 checks passed
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 16 additional findings in Devin Review.

Open in Devin Review

Comment on lines +812 to +823
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")
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 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.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants