CVE-2026-87858 Overview
CVE-2026-87858 is a confused deputy vulnerability [CWE-807] in Temporal Server. The History service decides whether a Workflow completion callback is internal by reading a caller-supplied HTTP header named source. An authenticated caller with only write permission in a single namespace can attach a callback that causes the server to issue an attacker-chosen state-changing POST against its own internal frontend administrative API. The internal frontend authorizes every request as a system administrator, so the caller reaches namespaces where they have no permission.
Critical Impact
Attackers can terminate Workflows in other namespaces, register namespaces, modify another namespace's configuration, and delete another namespace and its Workflows without ever reaching the internal frontend over the network.
Affected Products
- Temporal Server releases 1.30.0 and later, prior to 1.30.7
- Temporal Server releases 1.31.0 through 1.31.2
- Temporal Server releases 1.25.0 through 1.29.7 (narrower variant requiring the header to match the cluster ID UUID)
Discovery Timeline
- 2026-09-21 - CVE-2026-87858 published to the National Vulnerability Database
- 2026-09-22 - Last updated in NVD database
Technical Details for CVE-2026-87858
Vulnerability Analysis
The defect is a classic confused deputy problem. Temporal's History service uses a caller-controlled HTTP header to decide whether a delivered callback should be routed to the local internal frontend client instead of an external URL. When the header source is present and non-empty, the delivery code re-targets the request at the internal frontend and rewrites only the scheme and host. The caller's original path, query string, and request body are preserved verbatim.
The internal frontend, when deployed with its HTTP API enabled, authorizes every inbound request as a system administrator without requiring authentication material. The History service acts as the deputy: it holds administrative access and performs the request on the caller's behalf. The caller does not need network reachability to the internal frontend because the History service originates the call.
The affected routing logic exists in both the History State Machine (HSM) and CHASM callback delivery implementations.
Root Cause
The server trusts a caller-supplied header to make a trust boundary decision. Releases 1.30.0 and later accept any non-empty source header as sufficient to trigger internal re-targeting. Releases 1.25.0 through 1.29.7 require the header to exactly match the configured cluster ID UUID, a value that a namespace-scoped caller cannot read through the API, which makes exploitation materially harder in those versions.
Attack Vector
An authenticated caller with write permission in any single namespace attaches a completion callback to a Workflow. The callback URL host must satisfy the per-namespace component.callbacks.allowedAddresses allowlist, and the URL path is set to any Temporal HTTP API route. The callback header map includes a non-empty source header. When the History service delivers the callback, it swaps the scheme and host to point at the internal frontend and issues the POST. The internal frontend processes the request as a system administrator against the attacker-chosen path and body.
A deployment is exposed only when three conditions hold together: services.internal-frontend.rpc.httpPort is non-zero in static configuration, at least one component.callbacks.allowedAddresses rule admits a host in effective per-namespace dynamic configuration, and authorization is enabled. The allowlist is empty by default and the stock static topology does not include an internal frontend.
No verified proof-of-concept code has been published. See the linked pull requests for the corrective patches.
Detect Identity Attacks the Moment Authentication Fails
Singularity™ Identity surfaces credential misuse, lateral movement, and privilege escalation in real time, so you can contain the breach before it spreads.
Stop Identity AttacksDetection Methods for CVE-2026-87858
Indicators of Compromise
- Registered completion callbacks whose header map contains a non-empty source header, discoverable by calling DescribeWorkflowExecution against Workflows in each namespace.
- Unexpected namespace registrations, namespace configuration changes, or namespace deletions that do not correspond to authorized operator activity.
- Workflow terminations in namespaces whose owners did not initiate the action, correlated to callback delivery timestamps from the History service.
Detection Strategies
- Enumerate all Workflows using DescribeWorkflowExecution and flag any registered callback with a non-empty source header, since legitimate external callbacks have no reason to set this header.
- Compare the effective per-namespace component.callbacks.allowedAddresses allowlist against the deployment's known external callback destinations to identify overly broad rules.
- Audit namespace lifecycle events (create, update, delete) against ticketed change records to surface unauthorized administrative actions.
Monitoring Recommendations
- Raise the frontend HTTP API server log level above debug so the request method and URL of delivered callback requests are recorded. At default log levels the internal frontend does not log these requests, and their absence is not evidence of non-exploitation.
- Monitor History service outbound HTTP activity for POST requests that resolve to the internal frontend host and port.
- Alert on any change to services.internal-frontend.rpc.httpPort or to component.callbacks.allowedAddresses dynamic configuration.
How to Mitigate CVE-2026-87858
Immediate Actions Required
- Upgrade Temporal Server to v1.30.7, v1.31.3, or v1.32.0.
- Inventory existing Workflow callbacks with DescribeWorkflowExecution and remove any callback carrying a non-empty source header.
- Verify whether the deployment is exposed by checking that services.internal-frontend.rpc.httpPort is non-zero and that component.callbacks.allowedAddresses admits any host.
Patch Information
The fix is delivered in Temporal Server releases 1.30.7, 1.31.3, and 1.32.0. Corrective changes are tracked in GitHub Pull Request #11965, GitHub Pull Request #11982, and GitHub Pull Request #12085. The patches stop trusting the caller-supplied source header for internal routing decisions.
Workarounds
- Disable the internal frontend HTTP API by unsetting or zeroing services.internal-frontend.rpc.httpPort in static Server configuration.
- Empty the component.callbacks.allowedAddresses dynamic configuration for every namespace to deny all external callback URLs until the upgrade is applied.
- Do not rely on the error invalid url: url does not match any configured callback address as proof of a locked-down allowlist. It proves only that one tested URL did not match.
# Configuration example: disable internal frontend HTTP API in static config
services:
internal-frontend:
rpc:
httpPort: 0
# Deny all external callback URLs via dynamic configuration
component.callbacks.allowedAddresses:
- value: []
constraints: {}
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

