Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Validate forwarded grant agents before import#1674

Merged
kovtcharov-amd merged 1 commit into
amd:mainfrom
fallintoplace:fix/forwarded-agent-grant-validation
Jun 15, 2026
Merged

Validate forwarded grant agents before import#1674
kovtcharov-amd merged 1 commit into
amd:mainfrom
fallintoplace:fix/forwarded-agent-grant-validation

Conversation

@fallintoplace

Copy link
Copy Markdown
Contributor

Summary

  • reject forwarded connection imports when requested grant agent ids cannot be resolved
  • keep the no-agent import path unchanged
  • add regressions for unknown, mixed known/unknown, and missing-registry grant requests

Why

Forwarded imports resolved required scopes only for known agents, but still persisted every requested grant_agents value. An unknown id could therefore collapse import-time scope validation to [] and still receive a grant entry.

Validation

  • uv run python -m pytest tests/unit/connectors/test_router_forwarded.py - 16 passed, 2 skipped
  • uv run python -m pytest tests/unit/connectors/test_forwarded_import.py - 18 passed

@github-actions github-actions Bot added the tests Test changes label Jun 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Summary

This is a tight, correct security-hardening fix — approve once the docstring catches up. The forwarded-connection import previously skipped unknown grant_agents ids during scope resolution (if reg is None: continue) while import_forwarded_connection step 8 still persisted a grant for every id passed (api.py:382-384). An unknown id therefore collapsed required_scopes to [], sailed past the scope-coverage check, and still landed a grant entry — exactly the "silent skip + persist" gap. The fix resolves every requested id up front and fails loudly (404 unknown_agent), so nothing is imported on bad input. This aligns with GAIA's No-Silent-Fallbacks rule and the "leave the keyring untouched on a bad input" invariant the import path already documents.

The single most important thing: the behavior change is correct and the tests prove the right thing — they assert both the 404/503 status and that no grant or connection was persisted (list_agent_grants("google") == {}, connection GET → 404). That negative-persistence assertion is what makes this a trustworthy security test rather than just a status-code check.

No prompt-injection content in the diff. No secrets, no new dependencies, scope-clean (router + its test only).

Issues Found

🟢 Docstring no longer lists all failure modes (connectors.py:883-893)

The forward_connection docstring enumerates the loud failures (422 / 500 / 403 missing_scopes) but the two new ones — 404 unknown_agent and 503 registry not initialized — aren't mentioned. Since this docstring is the human-facing contract for the endpoint, keeping it as the single source of truth is worth one line:

    """Persist a forwarded grant — no browser/consent step (AC1, AC3, AC4).

    Fails loudly: empty client_id/refresh_token → 422 (pydantic ``min_length``);
    insecure keyring backend → 500; missing required scopes → 403 +
    ``missing_scopes``; an unresolvable ``grant_agents`` id → 404 +
    ``unknown_agent``; ``grant_agents`` set but the agent registry is
    unavailable → 503. Returns a metadata-only summary — never the refresh
    token or client secret.

🟢 Minor: behavior change for existing callers (non-blocking, no action needed)

A host app that was sending an unknown/stale grant_agents id now gets a 404 where it previously got a silent 201. That prior success was buggy (the grant was meaningless and it suppressed scope validation), so this is the right call — flagging only so it's a conscious decision, not a surprise. No deprecation path needed for fixing incorrect behavior.

Strengths

  • Tests verify the security property, not just the status code. Each new test asserts that no grant and no connection was persisted after rejection — catching the actual leak, not just the response shape (test_router_forwarded.py:84-157).
  • Fail-loud done right. The registry is None path raises 503 instead of falling through to an empty required set — closing the same skip-and-persist hole for the missing-registry case, with an actionable message.
  • Clean diff. Reuses the existing by_nsid lookup, drops the dead if reg is None: continue branch, and touches only the router and its test.

Verdict

Approve with suggestions — no blocking issues. The docstring nit is a one-line follow-up and can be applied with one click; the logic and tests are ready to merge as-is.

@kovtcharov-amd

Copy link
Copy Markdown
Collaborator

@itomek-amd, can you review this PR as it's related to connectors.

@kovtcharov-amd kovtcharov-amd added this pull request to the merge queue Jun 15, 2026
Merged via the queue into amd:main with commit cb4b0c5 Jun 15, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants