Skip to content

Auth binding: no error code represents a denial, and SEP-1686's disclosure requirement did not carry over #20

Closed as not planned

Description

@imran-siddique

PR #9 restored the Auth binding bullet that was dropped in the SEP-2663 port, and #19 locked it into 2026-07-28. Both versions of specification/draft/tasks.md now carry:

  • Auth binding. Servers MUST perform authentication and authorization checks on each task-related request to ensure that the client has permission to access a task.

That was the right restoration. Two things that follow from it are still open, and I think both are cheap to close.

1. There is no error code for a denial

Protocol Errors defines three cases: -32602 for an invalid or nonexistent taskId, -32603 for internal errors, and -32021 for a missing required client capability. None of them represents "this task exists and you may not access it".

So a server that performs the mandated check and denies has nothing to return that expresses the denial. In practice it returns -32602, which is also the answer for a task that never existed. A client cannot distinguish a refusal from a typo, and neither can anything reading the logs afterwards.

That collapse may well be deliberate, as an anti-enumeration measure, and if so it is a reasonable design. The request is that it be written down, because right now the spec mandates a check whose failure has no defined representation.

There is precedent for both halves of this. The same shape was raised for requestState integrity failures during SEP-2663 review, and resolved on 2026-05-04 by allowing a synchronous error path on tasks/update and tasks/cancel as SHOULD statements, which is why those two lines read SHOULD today. And the 2026-08-28 Agents WG meeting settled the general principle for agent operations: operation-specific error codes should be defined, reusing an existing protocol code where an appropriate one exists. Applying that same principle here would resolve this.

Concretely, either:

  • state normatively that servers MUST return -32602 for both unknown and unauthorized task IDs, and say why, or
  • define a distinct code for permission denied.

The first is probably correct given the cross-caller correlation argument. It just needs saying.

2. The disclosure requirement from SEP-1686 was not ported

SEP-2663's Motivation is explicit that the binding the Auth binding bullet requires is not always available:

all tasks should be bound to some sort of "authorization context," the implementation of which is left to individual servers according to their existing bespoke permission models. However, in many cases, it is not possible to perform this binding, in which case the task ID becomes the only line of defense against contamination.

With sessions removed by SEP-2567, there is, in the SEP's own words, "no other natural scope a server can define unilaterally". In those deployments the mandated check reduces to the bearer check that the neighbouring bullet permits, where a server MAY use task IDs as bearer tokens. Possession of the handle becomes the authorization. That is a different property from the one the MUST describes.

SEP-1686 handled this, at §8.1.2:

Receivers that do not implement session or authentication binding SHOULD document this limitation clearly, as task results may be accessible to any requestor that can guess the task ID.

That sentence did not survive into SEP-2663, and the Motivation paragraph that explains why it is needed is in the SEP rather than in tasks.md, so an implementer working from this repo alone sees the MUST with none of the context that bounds it. It looks like the same class of loss as the Auth binding bullet in #9: normative text dropped in a transition rather than deliberately removed.

Restoring an equivalent of §8.1.2 to Security Considerations would close it.

Why it matters now

The roadmap post of 22 August sets a direction of servers recognising agent identities "built on existing standards rather than pasted API keys and long-lived tokens", and names maturing this extension so it can move into the specification. In the draft schema ttlMs is number | null with null documented as unlimited. A handle that may be used as a bearer token and need never expire is worth reconciling with that direction before the extension moves into the core spec, where it gets more expensive to change.

What I checked

Read on 31 August 2026 against specification/draft/tasks.md and specification/2026-07-28/tasks.md at main in this repo, schema/draft/schema.ts, and seps/2663-tasks-extension.md at main in modelcontextprotocol/modelcontextprotocol. Error codes present in this repo's spec text: -32021, -32602, -32603. The words identity, revoke, revocation and audit appear zero times in either document; bearer appears once.

This is a reading of the specification, not a report against a running implementation. I have not tested what servers actually return for a denial today, so if the answer to point 1 is already settled somewhere I have not found, please point me at it and I will close this.

Activity

  1. LucaButBoring commented on Aug 31, 2026

    @LucaButBoring
    Contributor

    This is a protocol-wide concern, isn't it? There's no particular JSON-RPC error code for AuthN/AuthZ-related rejections in MCP in general, and in the core spec, MRTR requestState handling has the same issues. Tasks should ideally inherit whatever the protocol-wide handling is, except in the case where the trust boundary itself is meaningfully different from what is already specified, I think.

    There is also a broader tension here in that authorization as a general concept is only well-defined for HTTP-bound deployments; HTTP error codes are defined for AuthN/AuthZ failures here, but they only apply to OAuth-configured environments.

  2. imran-siddique commented on Sep 9, 2026

    @imran-siddique
    Author

    @LucaButBoring agreed on point 1, and thank you for the MRTR pointer, which I had not connected.

    You are right that a denial code is protocol-wide rather than a Tasks question. If requestState handling has the same collapse, then defining a Tasks-specific code would fragment the answer rather than settle it, and Tasks should inherit whatever the core spec lands on. The HTTP tension you raise makes that sharper rather than softer: if AuthN and AuthZ failures are only well-defined for OAuth-configured HTTP deployments, then a JSON-RPC-level denial has no home today for anyone else, and that is exactly the sort of thing one extension should not decide alone. Withdrawing point 1 from this issue.

    Point 2 is the half I would keep here, because nothing protocol-wide reaches it.

    Re-read specification/draft/tasks.md at 9263312d11a682ac83f83fe84794d4627efd22f5 before writing this. Security Considerations has four bullets, and the first two are:

    • Task ID unguessability. A server MAY use task IDs as bearer tokens for a server's stored state...
    • Auth binding. Servers MUST perform authentication and authorization checks on each task-related request...

    Those sit adjacent with nothing reconciling them. SEP-2663's own Motivation says the binding the MUST describes is often not possible, and that when it is not, the task ID becomes the only line of defence. In that deployment the MUST reduces to the bearer check the bullet above it permits, and possession of the handle becomes the authorization. That is a different security property, and an implementer reading this file alone cannot tell which one they are shipping.

    SEP-1686 §8.1.2 had the sentence that closed this: receivers that do not implement session or authentication binding SHOULD document the limitation clearly. Neither that sentence nor anything equivalent is in tasks.md. Searching all 911 lines, "document this limitation" and "guess the task ID" both return zero.

    This is not a code-space question and it does not depend on how the protocol-wide denial is resolved. It is one SHOULD in Security Considerations, restoring text that existed in the predecessor SEP and appears to have been lost in the port rather than removed on purpose, in the same way the Auth binding bullet itself was lost until #9 restored it.

    It also gets more expensive later. ttlMs is number | null with null documented as unlimited, so a handle that may serve as a bearer token need never expire, and the roadmap direction of moving this extension into the core specification is the point at which that becomes hard to change.

    Happy to send the bullet as a PR if that is useful, or leave it with you.

  3. imran-siddique commented on Oct 19, 2026

    @imran-siddique
    Author

    Closing alongside #33. Point 1 belongs with the protocol-wide error work, and point 2 can come back with #33 if the maintainers want it.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions