Declarative MCP approval can execute with transport headers different from those approved
Summary
The human-approval flow for declarative MCP tool invocations did not bind the evaluated transport headers to the operation being approved. The approval snapshot retained the evaluated server, tool, arguments, and connection name, but transport-header expressions were evaluated again after approval against mutable workflow state.
This created a time-of-check/time-of-use condition in which an operator could approve one effective authorization context but the MCP tool could execute under another.
Details
Before requesting approval, InvokeMcpToolExecutor evaluated the server URL, server label, tool name, arguments, headers, and connection name. Its durable ApprovalSnapshot retained the server, tool, arguments, and connection name so later workflow-state changes could not alter those approved values.
Headers were intentionally excluded from durable state because they can contain credentials. After approval, however, vulnerable versions called GetHeaders again and used the newly evaluated headers with the previously approved operation. No opaque identity or in-memory snapshot bound the original evaluated header set to the approval request.
In a shared requester/reviewer workflow session, a lower-trust requester could change ordinary workflow state while an MCP request was waiting for a separate operator's approval. If that state selected an authentication...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| Microsoft.Agents.AI.Workflows.Declarative NuGet | < 1.24.0 | 1.24.0 |
Details and references
### Summary The human-approval flow for declarative MCP tool invocations did not bind the evaluated transport headers to the operation being approved. The approval snapshot retained the evaluated server, tool, arguments, and connection name, but transport-header expressions were evaluated again after approval against mutable workflow state. This created a time-of-check/time-of-use condition in which an operator could approve one effective authorization context but the MCP tool could execute under another. ### Details Before requesting approval, `InvokeMcpToolExecutor` evaluated the server URL, server label, tool name, arguments, headers, and connection name. Its durable `ApprovalSnapshot` retained the server, tool, arguments, and connection name so later workflow-state changes could not alter those approved values. Headers were intentionally excluded from durable state because they can contain credentials. After approval, however, vulnerable versions called `GetHeaders` again and used the newly evaluated headers with the previously approved operation. No opaque identity or in-memory snapshot bound the original evaluated header set to the approval request. In a shared requester/reviewer workflow session, a lower-trust requester could change ordinary workflow state while an MCP request was waiting for a separate operator's approval. If that state selected an authentication or tenant context used by a header expression, the original approval could execute the same server, tool, and arguments with a different and more privileged authorization context. ### Proof of concept A sanitized reproduction uses two host-owned credentials and a mutable, non-secret selector: 1. Expose `LOW_TOKEN` and `PRIVILEGED_TOKEN` to the workflow environment scope as protected values. 2. Configure an MCP transport header with an expression equivalent to `If(System.LastMessage.Text = "LOW", Env.LOW_TOKEN, Env.PRIVILEGED_TOKEN)`. 3. Start the workflow with `System.LastMessage.Text` set to `LOW`. 4. Allow the executor to emit an approval request for the MCP operation. 5. Before the separate operator responds, change `System.LastMessage.Text` to `PRIVILEGED`. 6. Approve the original request without issuing a replacement approval. 7. Observe that a vulnerable version invokes the same MCP server, tool, and arguments with `PRIVILEGED_TOKEN`, even though the request was created while the effective header context was `LOW_TOKEN`. The requester does not need to know either credential or control the approval decision. With the fix, the evaluated headers captured for the original request are used exactly once. If an in-memory header snapshot is unavailable after checkpoint restoration, the old approval is invalidated and a new approval request is emitted. ### Impact Successful exploitation requires an application that: 1. permits a lower-trust requester to mutate workflow input while an approval is pending; 2. uses mutable workflow state to select MCP transport-header values; and 3. relies on a separate operator to approve the MCP action. The issue can cause unauthorized use of a privileged MCP capability. It does not disclose the underlying credential, and no exploitation or customer impact is known.