Temporal trusted the envelope but ignored the letter inside. A worker authorized for 'Namespace A' could embed commands targeting 'Namespace B' within a standard task completion response. Because the server only validated the worker's access to 'Namespace A', the cross-namespace commands were executed without a secondary permission check. Fixed by adding 'Deep Inspection' logic to the authorization layer.
A logic flaw in Temporal's gRPC authorization interceptor allows authenticated workers in one namespace to execute unauthorized commands (Signaling, Canceling, etc.) against workflows in a completely different namespace.
Temporal is the engine that drives reliable, durable execution for code that simply cannot fail. It's the backbone for microservices orchestration at scale. One of its key selling points for SaaS providers is the concept of Namespaces. These are supposed to be hard isolation boundaries—Tenant A plays in their sandbox, and Tenant B plays in theirs.
But here's the thing about boundaries in complex distributed systems: they are often enforced at the front door, but forgotten once you're inside the hallway. CVE-2025-14987 is a classic example of 'Shallow Inspection'.
Imagine a secure mailroom. The guard checks your ID badge to make sure you are allowed to send mail from your department. But once you hand over the envelope, the guard doesn't bother to check if the letter inside is a bomb addressed to the CEO. You're authorized to send mail, so the system assumes your mail is safe. In Temporal's case, the 'bomb' was a cross-namespace command.
To understand this bug, you have to understand the Temporal Worker lifecycle. Workers don't just execute code; they drive the state of the workflow. They poll for tasks, do some work, and then call RespondWorkflowTaskCompleted to tell the server what happened.
Crucially, this response payload contains a list of Commands. These are instructions for the server: 'Start a timer', 'Run an activity', or—more dangerously—'Signal an external workflow'.
Before the fix, Temporal's authorization interceptor checked permissions based solely on the source namespace of the request. If I am a worker for namespace-attacker, and I send a RespondWorkflowTaskCompleted request, the server asks: "Does this user have write access to namespace-attacker?" The answer is yes.
However, if I enabled system.enableCrossNamespaceCommands (which is true by default), I could embed a command inside that response saying: "Hey, please also signal workflow ID 123 in namespace-victim." The server, having already cleared me at the gate, would take that command and pass it to the History service for execution. The History service assumed the Frontend service did its job. The Frontend service assumed checking the envelope was enough. The result? A confusingly authorized bypass.
The fix required moving from shallow inspection to deep inspection. The developers had to crack open the RespondWorkflowTaskCompleted payload and iterate through every single command to see if it touched a namespace other than the caller's.
Here is the critical logic added to common/authorization/interceptor.go in commit b292a32bacdfa6472affd90f0a940408d5839cfa. It's a loop of scrutiny:
// The new sheriff in town: authorizeTargetNamespaces
func (a *Interceptor) authorizeTargetNamespaces(...) error {
// ... setup code ...
// Iterate through every command in the response
for _, cmd := range wftRequest.GetCommands() {
var targetNamespace string
var apiName string
// Switch on command type to find the 'target'
switch cmd.GetCommandType() {
case enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION:
if attr := cmd.GetSignalExternalWorkflowExecutionCommandAttributes(); attr != nil {
targetNamespace = attr.GetNamespace()
apiName = "SignalWorkflowExecution"
}
// ... (checks for Cancel and StartChild as well)
}
// THE FIX: If we are crossing boundaries, RE-AUTHORIZE.
if targetNamespace != "" && targetNamespace != sourceNamespace {
if err := a.Authorize(ctx, claims, &CallTarget{
APIName: api.WorkflowServicePrefix + apiName,
Namespace: targetNamespace, // Check permissions for the TARGET
Request: req,
}); err != nil {
return err
}
}
}
return nil
}This code explicitly checks if targetNamespace != sourceNamespace. If they differ, it forces a secondary Authorize call against the target. No more free rides.
Exploiting this requires a valid worker credential for any namespace on the cluster. It is an authenticated attack, which explains the CVSS score of 5.3, but in a multi-tenant environment, tenant isolation is everything.
The Setup:
namespace-red.namespace-blue.system.enableCrossNamespaceCommands is true (default).The Attack Chain:
SignalExternalWorkflowExecution command.namespace-blue and a known Workflow ID (often predictable or discoverable via enumeration).namespace-red.RespondWorkflowTaskCompleted to the server.The Payload:
{
"namespace": "namespace-red",
"commands": [
{
"commandType": "COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION",
"attributes": {
"namespace": "namespace-blue",
"workflowExecution": { "workflowId": "payment-process-101" },
"signalName": "CancelPayment",
"input": "..."
}
}
]
}The Result:
The server authorizes the request for namespace-red. It then processes the command list. It sees the signal for namespace-blue and executes it. The payment workflow in the victim namespace receives the signal and cancels the transaction. Chaos ensues.
Mitigation comes in two flavors: the patch and the configuration hammer.
1. The Patch (Preferred) Upgrade your Temporal cluster to version 1.29.2, 1.28.2, or 1.27.4. These versions include the deep inspection logic shown above. This allows legitimate cross-namespace communication (if you have the right permissions) while blocking unauthorized attempts.
2. The Configuration Hammer
If you can't patch immediately, you can simply turn off the feature that allows this behavior. Set the dynamic configuration system.enableCrossNamespaceCommands to false.
[!NOTE] Disabling this setting is a breaking change for workflows that legitimately rely on cross-namespace signaling. However, if you are running a strictly isolated multi-tenant environment, you probably wanted this disabled anyway.
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:L/SI:L/SA:L| Product | Affected Versions | Fixed Version |
|---|---|---|
Temporal Server Temporal | <= 1.27.3 | 1.27.4 |
Temporal Server Temporal | 1.28.0 - 1.28.1 | 1.28.2 |
Temporal Server Temporal | 1.29.0 - 1.29.1 | 1.29.2 |
| Attribute | Detail |
|---|---|
| CVE ID | CVE-2025-14987 |
| CVSS 4.0 | 5.3 (Medium) |
| CWE | CWE-863 (Incorrect Authorization) |
| Vector | CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:L/SI:L/SA:L |
| Impact | Cross-Namespace Command Execution |
| Patch Commit | b292a32bacdfa6472affd90f0a940408d5839cfa |
The product provides an authorization method that does not properly verify that the user has the necessary permissions to access the requested resource.
Get the latest CVE analysis reports delivered to your inbox.