# Patch Analysis: CVE-2026-5199 — Temporal Server Batcher Worker Cross-Namespace Authorization Bypass

## What the Fix Changes

Commit `90738c6200` ("Check namespaces in batch workflow") modifies:
- `service/worker/batcher/activities.go`
- `service/worker/batcher/activities_namespace_test.go` (new file)
- `service/worker/batcher/activities_test.go`

### Code Changes

1. **Replaces `checkNamespaceID` with `checkNamespaceProtobuf`**
   - Old `checkNamespaceID` only validated: `batchParams.NamespaceId == a.namespaceID.String()`
   - New `checkNamespaceProtobuf` validates **both**:
     - `batchParams.NamespaceId == a.namespaceID.String()`
     - `batchParams.Request.GetNamespace() == a.namespace.String()`

2. **Changes `BatchActivityWithProtobuf` to use worker-bound namespace**
   - Derives `ns := a.namespace.String()` after the check
   - Uses `ns` for `sdkClient` creation (`Namespace: ns`)
   - Passes `ns` to `startTaskProcessorProtobuf(..., ns, ...)` instead of `batchParams.Request.Namespace`

3. **Belt-and-suspenders in `startTaskProcessorProtobuf`**
   - The `ResetOperation` path now uses the `namespace` parameter instead of `batchOperation.Request.Namespace`
   - This was the only remaining place in `startTaskProcessorProtobuf` that still referenced `batchOperation.Request.Namespace`

4. **Removes SystemLocalNamespace exception from `checkNamespace`**
   - The old `checkNamespace` allowed system namespace to pass through
   - The fix adds an unconditional `namespace != a.namespace.String()` check

## Assumptions the Fix Makes

1. **The only attacker-controlled namespace fields are `NamespaceId` and `Request.Namespace`**
   - The fix assumes there are no other fields in `BatchOperationInput` or nested protobufs that carry namespace identifiers
   - Our analysis confirmed this: `BatchOperationInput` only has `namespace_id` and `request` fields, and `StartBatchOperationRequest` only has a top-level `Namespace` field

2. **String comparison is sufficient for namespace identity**
   - The fix uses exact Go string comparison (`!=`) for both `NamespaceId` and `Request.Namespace`
   - Temporal namespace names and IDs are case-sensitive in the registry, so this is correct

3. **All entry points to `BatchActivityWithProtobuf` go through `checkNamespaceProtobuf`**
   - The fix assumes there's no way to reach `startTaskProcessorProtobuf` or `BatchActivityWithProtobuf` without passing the check
   - Our analysis confirmed: the only callers are `BatchWorkflowProtobuf` (which validates via `ValidateBatchOperation` first, but that only checks non-empty, not matching) and the activity direct execution path (which goes through `BatchActivityWithProtobuf`)

## What Code Paths/Inputs the Fix Does NOT Cover

1. **Direct `startTaskProcessorProtobuf` call with forged namespace parameter**
   - If an attacker could somehow call `startTaskProcessorProtobuf` directly (bypassing `BatchActivityWithProtobuf`), the function would use whatever `namespace` string is passed to it
   - However, `startTaskProcessorProtobuf` is unexported and only called from `BatchActivityWithProtobuf`, so this is not a reachable attack path

2. **Other per-namespace workers**
   - The fix only covers the batcher worker (`service/worker/batcher/`)
   - Other per-namespace workers (scheduler, deployment, workerdeployment) were reviewed and do not have the same vulnerability pattern
   - Scheduler explicitly overwrites `req.Request.Namespace = a.namespace.String()` before using `FrontendClient`
   - Deployment and workerdeployment use registry-resolved namespace entries

3. **Non-protobuf `BatchActivity` path**
   - The old `BatchActivity` was already safe: it validates `batchParams.Namespace` via `checkNamespace` and uses it consistently
   - The fix did not change this path

## Is the Fix Complete?

**Yes, the fix is complete for the known vulnerability.**

Our variant testing confirmed:
- 7 distinct variant attempts were made
- All were blocked on v1.29.5
- Multiple operation types (SIGNAL, CANCEL, RESET) were tested
- Edge cases (nil Request, empty namespace, case-different name) were tested
- All were caught by `checkNamespaceProtobuf` or upstream validation

The fix is a **defense-in-depth** change that:
1. Validates both namespace identity forms (ID and name) at the activity boundary
2. Uses the worker-bound namespace for all downstream operations, never trusting the request
3. Adds regression tests that prevent future relaxation of these checks

## Comparison: Before vs After

### v1.29.4 (vulnerable)
```go
if err := a.checkNamespaceID(batchParams.NamespaceId); err != nil {
    return hbd, err
}
// Attacker can set batchParams.Request.Namespace to any namespace
// because it was never validated
startTaskProcessorProtobuf(ctx, batchParams, batchParams.Request.Namespace, ...)
```

### v1.29.5 (fixed)
```go
if err := a.checkNamespaceProtobuf(batchParams); err != nil {
    return hbd, err
}
ns := a.namespace.String() // worker-bound, trusted
startTaskProcessorProtobuf(ctx, batchParams, ns, ...)
```

## Recommendations for Closing Gaps

1. **Add namespace validation at the internal frontend client boundary** as a defense-in-depth measure. Even if a worker misbehaves, the internal frontend should reject requests for namespaces other than the one the worker is bound to.

2. **Audit all other per-namespace workers** for similar ID-vs-name validation gaps, especially any workers that accept protobuf input with nested namespace fields.

3. **Consider adding a `checkNamespace` call to the `BatchWorkflowProtobuf` workflow layer** in addition to the activity layer, so that even if the activity is invoked directly (bypassing the workflow), the check is still performed. (It already is, but belt-and-suspenders at the workflow layer could help.)

4. **Ensure that `ValidateBatchOperation` also checks namespace consistency** if it ever gains access to the worker-bound namespace context.
