Skip to content

fix(mcp): remove dead team authorization and harden dataset lookup - #319

Open
atomicdjt wants to merge 2 commits into
griddynamics:mainfrom
atomicdjt:fix/295-dataset-owner-team-authorization
Open

fix(mcp): remove dead team authorization and harden dataset lookup#319
atomicdjt wants to merge 2 commits into
griddynamics:mainfrom
atomicdjt:fix/295-dataset-owner-team-authorization

Conversation

@atomicdjt

@atomicdjt atomicdjt commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

This follow-up aligns PR #319 with the current MCP product surface and the maintainer review:

  • remove the unreachable team read-policy branch and its RAGFlow team API helper;
  • keep all and none; unsupported values such as team now fall back to the existing default (all);
  • preserve the live-path DatasetLookup collision fix so duplicate visible names are ambiguous instead of last-write-wins;
  • remove active project-dataset/team-policy documentation and stale generated examples.

Root cause and resulting behavior

All three registered MCP tools authorize only call_ctx.config.instruction_dataset, which is aia-{version}. Authorizer.can_read() short-circuits aia-* datasets before policy evaluation, and no MCP tool accepts a caller-selected dataset. The team/project authorization path therefore could not execute.

The remaining behavior is intentionally small:

  • aia-* instruction datasets are always readable.
  • Non-instruction datasets follow all or none; unknown policy values deny inside Authorizer.
  • Configuration normalizes unsupported ROSETTA_READ_POLICY values back to the existing default (all).
  • A visible dataset name shared by different IDs resolves to neither dataset by name.
  • Both datasets remain addressable by ID, and list_datasets() retains all visible IDs.

Removed

  • rosetta_mcp/services/_ragflow_team_api.py
  • team-policy helpers, constructor dependencies, and constants
  • team-policy and team-API tests
  • the obsolete policy-based authorization pattern
  • active documentation that advertised project datasets, cross-project intelligence, or team-policy behavior

DatasetLookup validation

Tests cover both duplicate-name insertion orders, ID lookup under ambiguity, repeated observation of the same ID, cache invalidation/refresh, and preservation of the full visible dataset list.

A refutation mutation temporarily restored last-write-wins behavior: 3 targeted tests failed as expected. Restoring ambiguity detection returned all 10 DatasetLookup tests to green. No mutation-only change was committed.

Validation

Passed locally on the final net diff:

  • focused authorization/config/dataset/tool-contract tests: 38 passed
  • full MCP suite: 339 passed
  • CLI suite: 56 passed
  • MyPy: 54 source files, no issues
  • Python bytecode compilation
  • MCP wheel and sdist build, including wheel-content checks
  • CLI wheel and sdist build
  • Twine checks for all four package artifacts
  • src/validate-types.sh Python validation
  • git diff --check and git fsck --no-dangling
  • clean synthetic merge with current upstream/main

Environment-limited checks:

  • verify_mcp.py verified the in-memory surface (3 tools and 1 resource template) but its 13 data-backed assertions could not run without ROSETTA_API_KEY; no live RAGFlow pass is claimed.
  • The aggregate pre-commit entrypoint stopped at the hooks build because src/hooks/node_modules/tsc are unavailable. Its runnable Python checks were executed directly above.
  • TypeScript workspace checks were skipped because their node_modules directories are absent.
  • The Jekyll documentation build was unavailable because Ruby/Bundler are not installed; source/web mirrors and inbound references to removed current docs were checked directly.

Net diff

Against the current merge base with main: 30 files changed, 114 insertions, 400 deletions.

Fixes #295

Signed-off-by: David Turner <davidelsey9513@gmail.com>
@atomicdjt
atomicdjt marked this pull request as ready for review August 21, 2026 12:29
@github-actions github-actions Bot added bug Something isn't working security labels Aug 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Rosetta Triage Review

Summary: Fixes #295 — under ROSETTA_READ_POLICY=team, Authorizer now resolves the exact requested dataset's owning tenant via DatasetLookup.tenant_id and checks membership only in that tenant's owner team, instead of granting access based on membership in any visible owner-role team.

Priority: High (security fix — closes a cross-tenant read-authorization bypass).

Findings:

  • Root cause correctly addressed: _check_team_membership now takes a resolved tenant_id and matches the owner team by exact tenant_id, rather than iterating all owner teams for the API token.
  • DatasetLookup.remember() now marks colliding dataset names as ambiguous (None in _name_to_id, entry dropped from _name_to_dataset) instead of silently overwriting on name collision — this also closes a related lookup-confusion risk where a stale/wrong dataset could be picked by name.
  • Fail-closed by design: missing DatasetLookup, missing/malformed tenant_id, ambiguous name, or unresolvable owner team all deny access rather than falling back to the old broad check.
  • Both production Authorizer construction sites (server.py, context.py) were updated to pass dataset_lookup, so the fix is wired end-to-end, not just in the class default.
  • Test coverage is thorough: cross-tenant denial, multi-team membership scoped by dataset owner, ambiguous names, malformed team/member/dataset shapes, error propagation, and default CallContext wiring are all covered.

Caveats:

  • dataset_lookup is optional and keyword-only; any out-of-tree Authorizer(...) caller using team policy without passing it now silently denies all team-policy reads (fail-closed, intentional per PR description, but worth flagging as a behavior change for downstream consumers).
  • DatasetLookup.list_datasets() changed its dedup key from name to id (_name_to_dataset_id_to_dataset), which now includes multiple datasets that share an ambiguous name where previously only one would appear — correct given the ambiguity fix, but changes the shape of results for any caller relying on name-based dedup.

Automated triage by Rosetta agent

@isolomatov-gd isolomatov-gd 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.

Thank you, @atomicdjt. This one needs a different change than the issue asked for.

ROSETTA_READ_POLICY=team is dead code. can_read returns True for any aia- name before reaching _evaluate (authorizer.py:43-45), and all three call sites pass call_ctx.config.instruction_dataset, which is INSTRUCTION_DATASET_TEMPLATE = "aia-{version}" (constants.py:87, config.py:425-426). No MCP tool accepts a dataset parameter. So _evaluate never receives a project-* name. Neither the #295 bypass nor this fix can execute.

Project datasets were removed from the product. The team policy and project-* handling are leftovers. No code supports them.

Our ask: delete them instead of hardening them. What is dead:

Code:

  • services/authorizer.py — the POLICY_TEAM branch, _check_team_membership, _get_team_api, the team_api constructor parameter, _normalize_email (used only on that path), and the project-* line in the class docstring
  • services/_ragflow_team_api.py — the whole file. authorizer.py is its only caller
  • constants.py:76,78POLICY_TEAM and its entry in VALID_POLICIES
  • tests/test_authorizer.py — the team-policy cases
  • tests/test_ragflow_team_api.py — the whole file

Documentation that advertises the setting, and should go with it:

  • src/rosetta-mcp-server/README.mdROSETTA_READ_POLICY appears in two env tables as a supported runtime setting
  • docs/MCP-ARCHITECTURE.md
  • docs/web/docs/mcp-architecture.md — the published website mirror
  • docs/PATTERNS/policy-based-authorization.md — built entirely on this policy, and it also documents a can_write that does not exist
  • docs/PATTERNS/env-backed-dataclass-config.md — its Authorizer example is already stale
  • the ownership section you added to docs/mcp/RAGFLOW.md becomes moot once the caller is gone

all and none stay. read_policy keeps working; team stops being a valid value and falls back to the default.

Keep the DatasetLookup change. It is the valuable half. get_id and get_dataset are on live paths. Before this change remember() was last-write-wins on a name collision, so a dataset named aia-r3 in another visible tenant could shadow the instruction dataset and be served as instructions. Your ambiguity marking turns that into a deny. Neither #295 nor the triage mentions it. Keep it here, or split it into its own PR if you prefer a narrower diff — your call.

For the record, the work itself checks out: 362 MCP, 39 focused, 56 CLI tests pass, mypy clean on 55 files. Your numbers reproduce exactly. Ten refutation attempts found no surviving and no newly introduced bypass; email and tenant-id normalization, the aia- short-circuit, ID-path access and pending invites all behave as described. The analysis was sound. The target was dead code, and that is on the issue, not on you.

Signed-off-by: David Turner <davidelsey9513@gmail.com>
@atomicdjt atomicdjt changed the title fix(mcp): scope team policy to dataset owner (#295) fix(mcp): remove dead team authorization and harden dataset lookup Aug 21, 2026
@atomicdjt

Copy link
Copy Markdown
Contributor Author

@isolomatov-gd Thank you for the correction. Follow-up commit 79e35004735d59f81d11b98c6e85b0182eb1a484 now implements the architecture you requested:

  • removed the unreachable team policy branch, RAGFlowTeamAPI, and their tests;
  • retained all/none, with team now treated as unsupported config and falling back to the existing default;
  • kept and strengthened the live-path DatasetLookup duplicate-name ambiguity protection, including both insertion orders and ID-path coverage;
  • removed current project-dataset/team-policy documentation and stale examples.

The PR title/body now describe the 30-file net diff rather than the superseded tenant-hardening approach. Final local validation is 38 focused, 339 MCP, and 56 CLI tests; MyPy is clean on 54 files; package builds/Twine checks passed. The new GitHub MCP build/test and CodeQL checks are all green. Ready for re-review.

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

Labels

bug Something isn't working security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ROSETTA] Authorizer team policy checks any owner team, not the dataset's owner

2 participants