Skip to content

read_note and search resolve unqualified names into other accessible workspaces #1432

Description

@phernandez

Summary

detect_project_from_identifier_prefix falls back to resolve_workspace_project_identifier, which searches index.entries_by_permalink across every workspace the account can access. So in a cloud/factory session an unqualified first segment can resolve to a project in a different workspace than the session's own — silently, and preferring the is_default workspace when a permalink is duplicated.

To be precise about severity: the index is scoped to workspaces the account can already access (personal plus teams), so this is not a cross-account data leak. It is a routing-correctness bug — the caller gets the wrong project, from the wrong workspace, with no indication that happened.

Reproduced form (in the POSIX resolver, before it was fixed)

Session bound to workspace beta (projects: research only); second accessible workspace acme holds notes:

resolve_project_path_route("notes/foo", project=None, project_id=None)
  -> ProjectPathRoute(project='acme/notes', path='foo', stripped=True)

Chain: the mount table declines (no mount named notes) → workspace-qualified parse declines (2 segments) → bare-prefix fallback → resolve_workspace_project_identifier("notes") matches the other workspace's project.

Status

Fixed for the POSIX tools in #1421 (b71d9d0a): that resolver now parses only fully qualified routes — workspace slug, then a project inside that workspace — and refuses an unaddressable name with UnqualifiedPathRefusedError listing the session's own mounts.

Not fixed for read_note and search, which still call detect_project_from_identifier_prefix and retain the bare-prefix fallback. They were left alone deliberately: unlike the POSIX verbs they have no mount table and no refusal rule, so simply deleting the fallback there would remove routing with nothing to replace it.

Why it is worth its own issue

The principle the POSIX surface settled on is refuse, don't default: an ambiguous or unqualified identifier should be refused with a message naming the addressable options, rather than resolved to a plausible candidate. read_note and search currently do the opposite, and they are the more heavily used tools.

Fixing them needs a decision this issue should settle: what an unqualified identifier means for a tool with no mount table. Options include restricting the fallback to the session's own workspace, adopting the same refusal with a list of candidates, or preferring the session workspace and only falling back when it has no match.

Note

detect_project_from_identifier_prefix's fallback branch is no longer exercised by the POSIX resolver, so #1421 added direct coverage in tests/mcp/test_project_context.py (hit and lookup-miss) to keep it tested while it remains in use.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions