title: 'MCP 2026-07-28 Stateless Spec: Async Task Abuse and OAuth 2.1 Misuse'
id: ATR-2026-02409
rule_version: 1
status: experimental
description: |
  Detects abuse of the two authority-bearing surfaces introduced by the Model Context Protocol 2026-07-28 specification: the Tasks extension (SEP-2663, extension id `io.modelcontextprotocol/tasks`) and the hardened OAuth authorization profile. The 2026-07-28 release deletes the `initialize`/`initialized` handshake and the `Mcp-Session-Id` session, so a server may answer any `tools/call` with a `CreateTaskResult` (`resultType: "task"`) and the client then drives the work with `tasks/get`, `tasks/update` and `tasks/cancel`. Two consequences follow. (1) The single human approval given at `tools/call` time is spent once, but the task it starts keeps executing for as long as `ttlMs` allows — a long `ttlMs` converts one approval into standing autonomous authority, and the `inputRequests` map that a task uses to reach back to the user is, in the spec's own words, not a higher-trust channel: it carries `elicitation/create` and `sampling/createMessage` payloads straight to the human or the model. (2) Because sessions are gone, a task ID is often the only thing binding a caller to its task; the spec therefore requires unguessable IDs and per-request authorization, and removed `tasks/list` outright on the grounds that a server which cannot bind tasks to an authorization context has the task ID as its only line of defence, making any listing endpoint unsafe to support at all. Detection targets the shapes an attacker actually puts on the wire — enumerable or injected `taskId` values on `tasks/get`/`tasks/update`/`tasks/cancel`, calls to the removed `tasks/list`/`tasks/result` methods against a 2026-spec server, `inputRequests` carrying standing-authorization or credential-phishing elicitations, `CreateTaskResult` handles with multi-week `ttlMs`, OAuth flows that are forbidden under the MCP profile (`response_type=token`, `code_challenge_method=plain`, `grant_type=password`), and protected-resource metadata that points the client at a plaintext, raw-IP or wildcard-redirect authorization server. Detection is confined to those wire shapes on purpose. A ninth prose layer, which flagged instruction text describing the technique, was removed after measurement: every one of its branches also fired on defensive security writing about the same technique, and separating the two requires excluding a negation or third-person attribution that sits BEFORE the matched verb ("never return a CreateTaskResult ..." vs "return a CreateTaskResult ..."), which needs lookbehind that RE2 does not provide. Instruction text that weaponises the Tasks extension is therefore out of scope for this rule; the traffic such an instruction produces is still caught by layers 1-5. CWE-863 (Incorrect Authorization), CWE-330 (Use of Insufficiently Random Values), CWE-693 (Protection Mechanism Failure), CWE-601 (Open Redirect).
author: ATR Community
date: 2026/07/28
schema_version: '0.1'
detection_tier: pattern
maturity: test
severity: high
references:
  owasp_llm:
    - LLM06:2025
    - LLM01:2025
  owasp_agentic:
    - ASI03:2026
    - ASI09:2026
    - ASI02:2026
  mitre_atlas:
    - AML.T0053 - AI Agent Tool Invocation
    - AML.T0098 - AI Agent Tool Credential Harvesting
    - AML.T0040 - AI Model Inference API Access
  mitre_attack:
    - T1078 - Valid Accounts
    - T1550.001 - Application Access Token
    - T1595 - Active Scanning
  cwe:
    - CWE-863
    - CWE-330
    - CWE-693
    - CWE-601
  external:
    - https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/
    - https://modelcontextprotocol.io/seps/2663-tasks-extension
    - https://tasks.extensions.modelcontextprotocol.io/
    - https://www.theregister.com/devops/2026/07/23/model_context_protocol_prepares_to_break_with_its_stateful_past/
    - https://www.akamai.com/blog/security-research/new-mcp-specification-security-teams-must-prepare
metadata_provenance:
  mitre_atlas: human-reviewed
  owasp_llm: human-reviewed
  owasp_agentic: human-reviewed
  external: human-reviewed
compliance:
  eu_ai_act:
    - article: '14'
      context: Article 14 human oversight assumes the approving person can still stop the operation. Under the MCP Tasks extension the operator approves a single tools/call and the resulting task then runs unattended for the whole ttlMs window, so a multi-week ttlMs or an inputRequests elicitation that asks the human to auto-approve everything that follows converts one supervised act into unsupervised autonomy. Detecting those two shapes is what keeps the Article 14 stop-and-intervene capability real after the handshake was removed from the protocol.
      strength: primary
    - article: '15'
      context: 'Article 15 cybersecurity and robustness requirements apply directly to the removal of protocol sessions in the 2026-07-28 specification: with Mcp-Session-Id gone, the task ID is the bearer of authorization, so enumerable task identifiers, calls to the removed tasks/list method, and OAuth flows that drop PKCE-S256 or accept implicit tokens are access-control failures that must be detected before an agent deployment is placed on the market.'
      strength: primary
  nist_ai_rmf:
    - function: Manage
      subcategory: MG.2.3
      context: MANAGE 2.3 requires mechanisms to supersede, disengage or deactivate AI systems that behave outside intended limits. A long-lived MCP task that keeps executing after its approving interaction has ended is exactly such a case; runtime detection of oversized ttlMs handles and of task-hosted requests for standing approval is the treatment that keeps the disengage path meaningful.
      strength: primary
    - function: Map
      subcategory: MP.5.1
      context: MAP 5.1 requires likely impacts of the AI system to be characterised. Organisations adopting the 2026-07-28 stateless MCP profile must register asynchronous task hijack via guessable task IDs, and elicitation phishing delivered through the task inputRequests channel, as first-class impact scenarios rather than transport details.
      strength: primary
    - function: Govern
      subcategory: GV.6.1
      context: GV.6.1 supplier risk management covers third-party MCP servers. A server that advertises a plaintext or raw-IP authorization server in its protected-resource metadata, or that registers wildcard redirect URIs, is failing the supplier control and must be gated before agent pipelines take tokens from it.
      strength: secondary
    - function: Measure
      subcategory: MS.2.7
      context: MEASURE 2.7 requires AI security and resilience to be evaluated and documented. Counting how often MCP exchanges carry enumerable task IDs, removed tasks/list calls, or OAuth 2.1 downgrades gives a measurable indicator of migration health during the 12-month deprecation window opened by the 2026-07-28 release.
      strength: secondary
  iso_42001:
    - clause: '8.1'
      context: 'Clause 8.1 operational planning and control must be updated for the stateless MCP profile: the control that used to live in session establishment now has to be enforced per request, so operational controls need to reject task-related requests whose identifiers are guessable and OAuth authorization requests that omit PKCE-S256 or use removed grant types.'
      strength: primary
    - clause: '8.3'
      context: Clause 8.3 AI risk treatment is supported by this rule, which implements runtime detection for the excessive-autonomy risk created when a single approval seeds a long-running MCP task that continues to act without further human involvement.
      strength: secondary
tags:
  category: excessive-autonomy
  subcategory: mcp-async-task-authority-abuse
  scan_target: mcp
  confidence: medium
agent_source:
  type: mcp_exchange
  framework:
    - mcp
    - any
  provider:
    - any
detection:
  condition: any
  false_positives:
    - Legitimate long-horizon batch work (data migration, model fine-tune, render farm) whose server honestly returns a multi-week ttlMs on CreateTaskResult — Layer 5 cannot distinguish a long job from laundered authority.
    - Load, soak and conformance test harnesses that poll tasks/get with synthetic sequential task IDs (task_1, task_2, ...) against their own server — Layer 1 sees the enumeration shape without the malicious intent.
    - 'Servers still serving 2025-11-25 clients in a mixed fleet during the 12-month deprecation window: a tasks/list or tasks/result call is legitimate there, and Layer 2 will fire whenever a 2026 marker (resultType, extension id, protocolVersion) happens to appear in the same buffer.'
    - Genuine credential elicitation inside a task that happens to use urgency wording ('your session has expired, please re-enter your access token') — Layer 4 cannot tell an honest re-auth prompt from a phishing one.
    - Development and staging OAuth setups that intentionally use plain PKCE or an http:// authorization server on a trusted network.
    - Attacker-controlled text quoted verbatim inside an otherwise benign MCP tool response (a scraped page, an issue body, a log line). Every layer matches on flat text with no notion of which side of the exchange produced it, so a task-shaped payload echoed back by a search tool reads identically to one the server actually emitted.
    - 'Coverage boundary rather than a false positive, recorded here so no one over-reads this rule: it produces NO verdict on a SKILL.md scan. detection.condition is ''any'' and the engine short-circuits on the first matching condition, so matchedConditions is always 1, while a scan_target: mcp rule needs ceil(7 * 0.3) = 3 matched conditions on the skill path. Verified against engine.scanSkill() with a document carrying four distinct true-positive payloads. Treat this rule as MCP-exchange runtime coverage only.'
    - 'Scope boundary, recorded because it was measured and then acted on: prose is NOT covered. The prose layer this rule shipped with was removed after all three of its branches fired on defensive security writing (threat models, hardening checklists, pentest reports, and the migration guidance that tells clients to iterate their stored task IDs now that tasks/list is gone). Text that merely describes, teaches, or warns about MCP async-task abuse will not be flagged by this rule, by design.'
  conditions:
    - field: content
      operator: regex
      value: (?i)(?:"method"\s*:\s*"tasks/(?:get|update|cancel)"[\s\S]{0,300}"taskId"\s*:\s*"(?:[0-9]{1,7}|task[_\-]?[0-9]{1,7}|0{8,}[0-9a-f\-]{0,28}|[*%]|\.\.[\\/][^"]{0,60}|[^"]{0,60}(?:%27|\x27\s*(?:or|and|union)\s|;\s*drop\s)[^"]{0,60})"|"taskId"\s*:\s*"(?:[0-9]{1,7}|task[_\-]?[0-9]{1,7}|0{8,}[0-9a-f\-]{0,28}|[*%]|\.\.[\\/][^"]{0,60}|[^"]{0,60}(?:%27|\x27\s*(?:or|and|union)\s|;\s*drop\s)[^"]{0,60})"[\s\S]{0,300}"method"\s*:\s*"tasks/(?:get|update|cancel)")
      description: tasks/get, tasks/update or tasks/cancel carrying a guessable, wildcard, traversal or injected taskId — cross-caller task hijack attempt against a sessionless MCP server (SEP-2663 requires unguessable IDs)
    - field: content
      operator: regex
      value: (?i)(?:"method"\s*:\s*"tasks/(?:list|result)"[\s\S]{0,500}(?:io\.modelcontextprotocol/tasks|"protocolVersion"\s*:\s*"2026-(?:06-30|07-28)"|"resultType"\s*:\s*"(?:task|complete)")|(?:io\.modelcontextprotocol/tasks|"protocolVersion"\s*:\s*"2026-(?:06-30|07-28)"|"resultType"\s*:\s*"(?:task|complete)")[\s\S]{0,500}"method"\s*:\s*"tasks/(?:list|result)")
      description: Call to the removed tasks/list or tasks/result method inside an exchange that already carries 2026-07-28 markers — enumeration of other callers' task IDs or a protocol downgrade probe
    - field: content
      operator: regex
      value: (?i)"inputRequests"\s*:\s*\{[\s\S]{0,800}"method"\s*:\s*"(?:elicitation/create|sampling/createMessage)"[\s\S]{0,800}(?:auto[\s_\-]?approve|approve\s+(?:all|any|every|future|subsequent|remaining)|do\s*n[o\x27]?t\s+ask\s+(?:me\s+)?again|(?:disable|skip|bypass|suppress)\s+(?:the\s+)?(?:confirmation|approval|consent|human\s+review)|remember\s+(?:this\s+)?(?:approval|consent|choice)\s+for\s+all|grant\s+(?:full|admin|root|unrestricted|permanent)\s+(?:access|permission|authority|privileges)|apply\s+to\s+all\s+(?:future|subsequent|remaining))
      description: MCP task inputRequests elicitation asking the human to grant blanket or permanent approval — one consent laundered into standing autonomy for the rest of the task
    - field: content
      operator: regex
      value: (?i)"inputRequests"\s*:\s*\{[\s\S]{0,800}"(?:message|systemPrompt|text)"\s*:\s*"(?:[^"]{0,300}(?:re-?authenticate|verify\s+your\s+(?:identity|account)|session\s+(?:has\s+)?expired|security\s+(?:check|verification)|confirm\s+your\s+(?:credentials|password)|account\s+(?:will\s+be\s+)?(?:locked|suspended)|urgent(?:ly)?|immediately)[^"]{0,300}(?:api[\s_\-]?keys?|secret\s+(?:access\s+)?key|client[\s_\-]?secret|password|passphrase|private[\s_\-]?key|access[\s_\-]?token|refresh[\s_\-]?token|seed\s+phrase|mnemonic|recovery\s+(?:code|phrase)|credit[\s_\-]?card|ssh[\s_\-]?key)|[^"]{0,300}(?:api[\s_\-]?keys?|secret\s+(?:access\s+)?key|client[\s_\-]?secret|password|passphrase|private[\s_\-]?key|access[\s_\-]?token|refresh[\s_\-]?token|seed\s+phrase|mnemonic|recovery\s+(?:code|phrase)|credit[\s_\-]?card|ssh[\s_\-]?key)[^"]{0,300}(?:re-?authenticate|verify\s+your\s+(?:identity|account)|session\s+(?:has\s+)?expired|security\s+(?:check|verification)|confirm\s+your\s+(?:credentials|password)|account\s+(?:will\s+be\s+)?(?:locked|suspended)|urgent(?:ly)?|immediately))
      description: MCP task inputRequests rendering an urgency-framed credential prompt — rich interactive elicitation phishing through a channel the spec says carries no extra trust
    - field: content
      operator: regex
      value: (?i)(?:"resultType"\s*:\s*"task"[\s\S]{0,400}"ttlMs"\s*:\s*[0-9]{10,}|"ttlMs"\s*:\s*[0-9]{10,}[\s\S]{0,400}"resultType"\s*:\s*"task")
      description: CreateTaskResult handing back a task whose ttlMs exceeds ~11.6 days — a single tools/call approval seeding weeks of unattended execution
    - field: content
      operator: regex
      value: (?i)(?:(?:modelcontextprotocol|mcpServers|oauth-protected-resource|mcp[_\-]?server|/mcp[/?"\s]|"?resource"?\s*[:=]\s*"?https?(?::|%3A)[^"&\s]{0,120}mcp)[\s\S]{0,600}(?:"?response_type"?\s*[:=]\s*"?(?:token\b|id_token(?:%20|\+|\s)+token)|"?code_challenge_method"?\s*[:=]\s*"?plain\b|"?grant_type"?\s*[:=]\s*"?password\b)|(?:"?response_type"?\s*[:=]\s*"?(?:token\b|id_token(?:%20|\+|\s)+token)|"?code_challenge_method"?\s*[:=]\s*"?plain\b|"?grant_type"?\s*[:=]\s*"?password\b)[\s\S]{0,600}(?:modelcontextprotocol|mcpServers|oauth-protected-resource|mcp[_\-]?server|/mcp[/?"\s]|"?resource"?\s*[:=]\s*"?https?(?::|%3A)[^"&\s]{0,120}mcp))
      description: MCP authorization exchange using an implicit grant, plain PKCE or the resource-owner password grant — flows removed or forbidden under the MCP OAuth profile, yielding tokens the client cannot bind to a resource
    - field: content
      operator: regex
      value: (?i)(?:"redirect_uris?"\s*:\s*(?:\[\s*)?"https?://(?:[a-z0-9\-]{1,63}\.)+[a-z]{2,24}[^"]{0,80}\*|"?redirect_uri"?\s*[:=]\s*"?https?(?::|%3A)(?://|%2F%2F)[^"&\s]{0,80}@[^"&\s]{0,80}|"authorization_servers"\s*:\s*\[\s*"http://|"authorization_servers"\s*:\s*\[\s*"https?://(?:[0-9]{1,3}\.){3}[0-9]{1,3})
      description: MCP OAuth metadata or client registration advertising a plaintext / raw-IP authorization server, a wildcard redirect URI on a public host, or a userinfo-confusion redirect — authorization-code and token interception setup
response:
  actions:
    - alert
    - block_tool
    - escalate
  auto_response_threshold: critical
  message_template: |
    [ATR-2026-02409] HIGH: MCP 2026-07-28 async-task or OAuth authority abuse detected. The exchange shows one of: a tasks/get, tasks/update or tasks/cancel call carrying a guessable or injected taskId; a call to the removed tasks/list or tasks/result method against a 2026-spec server; a task inputRequests elicitation asking the human for blanket approval or for credentials under urgency framing; a CreateTaskResult whose ttlMs grants weeks of unattended execution; or an MCP authorization flow using a removed OAuth grant, plain PKCE, or an untrusted authorization server. Hold the task, re-verify that the task ID is bound to this caller, and require a fresh human approval before the task continues. Treat any credential prompt arriving via inputRequests as untrusted input, not as a first-party dialog.
confidence: 70
