Skip to content

Authorize resourceSubscriptions on subscriptions/listen #1307

Description

@Kehrlann

Problem

On the modern server, subscriptions/listen accepts any URI in resourceSubscriptions. SubscriptionsFeature never checks that the caller is allowed to access it. Change events are server-wide, so on a multi-tenant server a client can subscribe to another principal's URI and receive notifications/resources/updated for it. Only timing and existence leak, not content.

Desired outcome

Server implementers have a hook to gate subscriptions per URI.

Example, non-normative implementation:

  • Add a canSubscribe(ctx, uri) hook to the resource repositories, defaulting to allow. subscriptions/listen drops every URI it denies.

Notes

  • Filtering by find isn't suitable: subscribing to a resource that doesn't exist yet is legitimate.
  • List-changed notifications have no payload and go to every listener. Document on McpChangeFeed that they aren't scoped per principal.

Credit

Reported by @hackking007

Activity

  1. added this to the 2.2.0-M2 milestone on Nov 2, 2026
  2. self-assigned this
    on Nov 2, 2026
  3. added
    P1Significant bug affecting many users, highly requested feature
    modernv3 covering the 2026-07-28 spec
    on Nov 2, 2026
  4. modified the milestones: 2.2.0-M2, 2.2.0-M3 on Nov 2, 2026
  5. hackking007 commented on Nov 3, 2026

    @hackking007

    Thanks Daniel - appreciate the fast triage and the credit on #1307. The canSubscribe(ctx, uri) hook is the right shape; happy to review the PR against the cross-session cases when it's up.

  6. media-ctrl commented on Nov 8, 2026

    @media-ctrl

    The hook is the right shape, and one detail matters more than the default: allow-by-default fails open, so the hook should be able to distinguish three outcomes rather than two. Allowed, denied, and not-yet-existing. Your point that filtering via find is unsuitable is the correct constraint, and it is worth writing down explicitly, because an implementer reading only "drop the denied URIs" will reach for find and break forward-declared subscriptions.

    Two things I would add to the proposal.

    The denial path needs to be non-enumerable. Dropping a denied URI silently and returning an error for it are observably different from a client's side, and either one combined with a timing difference leaks existence even when content does not. Pick one behaviour for both denied and nonexistent and apply it uniformly.

    Put the gate on whatever resolves URIs, not on the subscriptions/listen handler. If the check lives in the route, the next bulk subscribe or renamed endpoint becomes a second place to forget it. A single enforcement point that every subscription path calls also makes the audit story simple: one function, one default, one place to flip it.

    Documenting on McpChangeFeed that list-changed notifications are not per-principal is worth doing even after the hook lands, since that distinction is invisible in the notification payload and easy for multi-tenant implementers to assume away.

  7. hackking007 commented on Nov 8, 2026

    @hackking007

    Agree with all three, and the non-enumerable denial path is the important one - it closes the exact residual the issue calls out ("only timing and existence leak"). If denied and not-yet-existing return the same shape and comparable timing, there's no oracle left to work with.

    One addition for the enforcement-point discussion: canSubscribe(ctx, uri) gates entry, but a subscription that was legitimately allowed can later become unauthorized while it's still live - a resource re-homed to another tenant, or an ACL change on an existing URI. If the only check is at subscribe time, the listener keeps receiving notifications/resources/updated after it should have lost access. So the single enforcement point media-ctrl describes should ideally be reachable from the dispatch path as well (re-check, or invalidate affected subscriptions on ACL/ownership change), not only from the subscribe path. Otherwise the hook is correct but revocation is a second, quieter version of the same cross-principal leak.

    Happy to review the PR against both the subscribe-time and dispatch-time cross-session cases when it's up.

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

Metadata

Metadata

Assignees

Labels

P1Significant bug affecting many users, highly requested featurearea/servermodernv3 covering the 2026-07-28 spec

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions