# REPRO-2026-00184: Temporal Server: batcher worker cross-namespace authorization bypass (BatchActivityWithProtobuf) ## Summary Status: published Severity: medium Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00184 CVE: CVE-2026-5199 ## Package Name: temporal Ecosystem: go Affected: 1.29.0 – 1.29.4 (and 1.30.0 – 1.30.2) Fixed: 1.29.5 ## Root Cause # RCA Report: CVE-2026-5199 — Temporal Server Batcher Worker Cross-Namespace Authorization Bypass ## Summary CVE-2026-5199 is an authorization bypass in Temporal Server's per-namespace batcher worker (`service/worker/batcher/activities.go`). The `BatchActivityWithProtobuf` activity validated only `batchParams.NamespaceId` (a namespace UUID) via `checkNamespaceID`, but then forwarded `batchParams.Request.Namespace` (a namespace **name**) to the internal frontend client. Because the internal frontend connection runs with `NoopClaimMapper → RoleAdmin`, any namespace name supplied by an attacker was executed unconditionally. An authenticated attacker with writer access to one namespace could craft a `BatchOperationInput` whose `NamespaceId` matched their own namespace (passing the check) while `Request.Namespace` targeted a victim namespace, causing the batcher to signal, cancel, terminate, or reset workflows in the victim namespace without authorization. ## Impact - **Package/Component**: `go.temporal.io/server/service/worker/batcher` — specifically `BatchActivityWithProtobuf` and `startTaskProcessorProtobuf` - **Affected versions**: `1.29.0` – `1.29.4` and `1.30.0` – `1.30.2` - **Fixed versions**: `v1.29.5` - **Risk level**: Medium (CVSS 3.1 ~4.2, Authenticated) - **Consequences**: Cross-namespace workflow mutation (signal, cancel, terminate, reset) by an authenticated principal authorized in a different namespace ## Root Cause The vulnerable code path in `service/worker/batcher/activities.go` (`v1.29.4`) is: 1. `BatchActivityWithProtobuf` receives a `BatchOperationInput` protobuf. 2. It calls `checkNamespaceID(batchParams.NamespaceId)` which only verifies the namespace UUID matches the worker-bound namespace ID. 3. It then uses `batchParams.Request.Namespace` for downstream operations: - `config.namespace = batchParams.Request.Namespace` - `startTaskProcessorProtobuf(..., batchParams.Request.Namespace, ...)` 4. `startTaskProcessorProtobuf` passes this attacker-controlled namespace name to all internal frontend client calls (`SignalWorkflowExecution`, `CancelWorkflowExecution`, `TerminateWorkflowExecution`, `ResetWorkflowExecution`, etc.). 5. The internal frontend client (`NoopClaimMapper → RoleAdmin`) does not perform namespace-scoped authorization checks on these internal calls, so the operation executes against the victim namespace. The fix commit [`90738c6200`](https://github.com/temporalio/temporal/commit/90738c6200) ("Check namespaces in batch workflow") replaced `checkNamespaceID` with `checkNamespaceProtobuf`, which validates **both**: - `batchParams.NamespaceId == worker-bound namespace ID` - `batchParams.Request.Namespace == worker-bound namespace name` It also changed `BatchActivityWithProtobuf` to derive the namespace from `a.namespace.String()` (the worker-bound name) rather than from the protobuf request, and updated `startTaskProcessorProtobuf` to use the already-validated `namespace` parameter instead of `batchOperation.Request.Namespace`. ## Reproduction Steps The reproduction script is `repro/reproduction_steps.sh`. It performs the following: 1. Clones the `temporalio/temporal` repository (or uses an existing clone). 2. Builds the `temporal-server` binary from source for both `v1.29.4` (vulnerable) and `v1.29.5` (fixed). 3. Starts a real Temporal Server with SQLite in-memory persistence (`development-sqlite.yaml`) and `--allow-no-auth`. 4. Polls port `127.0.0.1:7233` until the gRPC frontend is listening. 5. Runs a standalone Go program (`repro_main.go`) that: - Connects to the real server via gRPC - Creates two namespaces: `attacker-ns` and `victim-ns` - Starts a victim workflow in `victim-ns` - Gets the namespace ID for `attacker-ns` - Constructs a forged `BatchOperationInput` where `NamespaceId` matches `attacker-ns` but `Request.Namespace` is `victim-ns` - Directly starts the internal `temporal-sys-batch-workflow-protobuf` workflow in `attacker-ns` with this forged payload - Waits 15 seconds for the per-namespace batcher worker to execute - Queries the victim workflow history for `WORKFLOW_EXECUTION_SIGNALED` events 6. **On v1.29.4**: the victim workflow history contains a signal event, proving the cross-namespace bypass. 7. **On v1.29.5**: the victim workflow history contains zero signal events, proving the fix blocks the bypass. Run the script: ```bash ./repro/reproduction_steps.sh ``` ## Evidence ### Vulnerable behavior (v1.29.4) Log location: `logs/vuln_repro.log` Key excerpt: ``` VULNERABLE: victim workflow received 1 signal(s) ``` The victim workflow in `victim-ns` received a `test-signal` that was injected by the batcher worker running in `attacker-ns`, proving the cross-namespace authorization bypass. ### Fixed behavior (v1.29.5) Log location: `logs/fix_repro.log` Key excerpt: ``` FIXED: victim workflow received 0 signals exit status 1 ``` The activity returned `namespace mismatch` before any frontend client call was made, and the victim workflow received zero signals. ### Server logs Server startup and shutdown logs are captured in: - `logs/server_v1.29.4.log` - `logs/server_v1.29.5.log` ### Runtime manifest `repro/runtime_manifest.json` records the test outcomes: ```json { "cve": "CVE-2026-5199", "vulnerable_version": "v1.29.4", "fixed_version": "v1.29.5", "vulnerability_confirmed": true, "fix_verified": true } ``` ## Recommendations / Next Steps - **Upgrade** to Temporal Server `v1.29.5` or later (or `v1.30.3` / `v1.31.0` depending on the release line). - **Verify** that the `checkNamespaceProtobuf` validation exists in any custom builds or forks. - **Testing**: Add regression tests that verify both `NamespaceId` and `Request.Namespace` mismatches are rejected by the batcher worker. - **Defense in depth**: Consider adding namespace validation at the internal frontend client boundary as well, so that even if a worker misbehaves, the internal frontend rejects cross-namespace requests. ## Additional Notes - **Idempotency**: The reproduction script is idempotent. It cleans up server processes by PID, switches git refs, and rebuilds the binary each run. It has been verified to produce consistent results across consecutive executions. - **Edge cases**: The reproduction uses the `SIGNAL` batch type, but the same bypass applies to `CANCEL`, `TERMINATE`, `RESET`, `DELETE`, `UPDATE_EXECUTION_OPTIONS`, `UNPAUSE_ACTIVITY`, `RESET_ACTIVITY`, and `UPDATE_ACTIVITY_OPTIONS` because all paths in `startTaskProcessorProtobuf` use the same attacker-controlled `namespace` parameter. - **Real server**: The reproduction stands up a real Temporal Server process with all services (frontend, history, matching, worker) using SQLite in-memory persistence. The exploit is delivered through the real gRPC frontend endpoint (`StartWorkflowExecution`) and the internal per-namespace worker actually executes the batch workflow and activity. The only artifact that differs from production is the use of `--allow-no-auth` (which disables authorization) so that the test client can create namespaces and start system workflows without needing a full auth stack. ## Reproduction Details Reproduced: 2026-05-28T17:22:05.162Z Duration: 4370 seconds Tool calls: 401 Turns: 368 Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00184 pruva-verify CVE-2026-5199 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00184&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00184/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-5199 ## Artifacts - bundle/repro/rca_report.md (analysis, 7165 bytes) - bundle/repro/reproduction_steps.sh (reproduction_script, 9344 bytes) - bundle/vuln_variant/rca_report.md (analysis, 8122 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 2344 bytes) - bundle/context.json (other, 3211 bytes) - bundle/metadata.json (other, 671 bytes) - bundle/ticket.md (ticket, 6228 bytes) - bundle/repro/runtime_manifest.json (other, 700 bytes) - bundle/repro/validation_verdict.json (other, 1915 bytes) - bundle/vuln_variant/variant_test.go (other, 13061 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 5940 bytes) - bundle/vuln_variant/variant_manifest.json (other, 3209 bytes) - bundle/vuln_variant/validation_verdict.json (other, 2497 bytes) - bundle/logs/server_v1.29.5.pid (other, 6 bytes) - bundle/logs/fix_repro.log (log, 56 bytes) - bundle/logs/variant_v1.29.4.log (log, 8699 bytes) - bundle/logs/server_v1.29.5.log (log, 93509 bytes) - bundle/logs/vuln_test.log (log, 490 bytes) - bundle/logs/server_v1.29.4.pid (other, 6 bytes) - bundle/logs/vuln_repro.log (log, 49 bytes) - bundle/logs/variant_test_v1.29.4.log (log, 8697 bytes) - bundle/logs/fix_test.log (log, 27073 bytes) - bundle/logs/server_v1.29.4.log (log, 85867 bytes) - bundle/logs/variant_v1.29.5.log (log, 21794 bytes) - bundle/logs/variant_test_v1.29.5.log (log, 21791 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00184 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00184/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00184 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev