Skip to content

CVE-2026-5199: Verified Repro With Script Download

CVE-2026-5199: Temporal Server: batcher worker cross-namespace authorization bypass BatchActivityWithProtobuf

CVE-2026-5199 is verified against temporal · go. Affected versions: 1.29.0 – 1.29.4 (and 1.30.0 – 1.30.2). Fixed in 1.29.5. Vulnerability class: Auth Bypass. This low reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00184.

REPRO-2026-00184 temporal · go Auth Bypass Variant found May 28, 2026 CVE entry ↗ .txt
Severity
LOW
CVSS
2.3
Reproduced in
72m 49s
Tool calls
401
Spend
$6.46
01 · Overview

What Is CVE-2026-5199?

CVE-2026-5199 is a medium-severity authorization bypass (CWE-285) in Temporal Server's per-namespace batcher worker, which lets an authenticated caller with writer access to one namespace mutate workflows in a different, victim namespace. Pruva reproduced it (reproduction REPRO-2026-00184).

02 · Severity & CVSS

CVE-2026-5199 Severity & CVSS Score

CVE-2026-5199 is rated low severity, with a CVSS base score of 2.3 out of 10.

LOW threat level
2.3 / 10 CVSS base
Weakness CWE-285 (Improper Authorization) — Improper Authorization

Low — limited impact or hard to exploit. Address in the normal cycle.

03 · Affected Versions

Affected temporal Versions

temporal · go versions 1.29.0 – 1.29.4 (and 1.30.0 – 1.30.2) are affected.

How to Reproduce CVE-2026-5199

$ pruva-verify REPRO-2026-00184
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00184/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-5199

Reproduced by Pruva's autonomous agents — 401 tool calls over 1h 13m. Full root-cause analysis and the complete transcript are below.

Variants tested

Systematic variant analysis of CVE-2026-5199 (Temporal Server batcher worker cross-namespace authorization bypass). Seven distinct variant hypotheses were tested against v1.29.4 (vulnerable) and v1.29.5 (fixed). No bypass or alternate trigger was found on the fixed version. The fix is comprehensive.

How the agent worked 1,506 events · 401 tool calls · 1h 13m
1h 13mDuration
401Tool calls
363Reasoning steps
1,506Events
8Dead-ends
Agent activity over 1h 13m
Support
15
Repro
775
Variant
712
0:0072:49

Root Cause and Exploit Chain for CVE-2026-5199

Versions: 1.29.0 – 1.29.4 and 1.30.0 – 1.30.2Fixed: v1.29.5

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.

  • Package/Component: go.temporal.io/server/service/worker/batcher — specifically BatchActivityWithProtobuf and startTaskProcessorProtobuf
  • Affected versions: 1.29.01.29.4 and 1.30.01.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 ("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:

./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:

{
  "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.

Variant Analysis & Alternative Triggers for CVE-2026-5199

Versions: versions (original bug): 1.29.0 – 1.29.4, 1.30.0 – 1.30.2Fixed: v1.29.5, v1.30.3

No distinct bypass or alternate trigger was found on the fixed version (v1.29.5). Seven distinct variant hypotheses were systematically tested against both the vulnerable (v1.29.4) and fixed (v1.29.5) versions. All alternate triggers were blocked by the fix commit 90738c6200. The fix comprehensively closes the cross-namespace authorization bypass by validating both NamespaceId and Request.Namespace against the worker-bound namespace before any downstream operation, and by using the worker-bound namespace for all internal frontend client calls.

Fix Coverage / Assumptions

The original fix relies on the invariant that a per-namespace batcher worker must only ever operate on its own bound namespace. To enforce this, the fix makes three key assumptions:

  1. Both namespace identity forms must match: batchParams.NamespaceId (UUID) and batchParams.Request.Namespace (name) must both equal the worker's bound namespace.
  2. The worker-bound namespace is authoritative: After validation, ns := a.namespace.String() is used for all SDK client creation and startTaskProcessorProtobuf calls.
  3. String comparison is sufficient: Exact Go string equality (!=) is used because Temporal namespace names and IDs are case-sensitive in the registry.

The fix explicitly covers:

  • BatchActivityWithProtobuf activity entry point
  • All batch operation types (SIGNAL, CANCEL, TERMINATE, RESET, DELETE, UPDATE_EXECUTION_OPTIONS, UNPAUSE_ACTIVITY, RESET_ACTIVITY, UPDATE_ACTIVITY_OPTIONS)
  • The BatchWorkflowProtobuf workflow that invokes the activity

The fix does NOT cover:

  • Direct invocation of startTaskProcessorProtobuf with a forged namespace parameter (but this function is unexported and unreachable without first bypassing BatchActivityWithProtobuf)
  • Other per-namespace workers (scheduler, deployment, workerdeployment), which were reviewed and found not to have the same vulnerability pattern

Variant / Alternate Trigger

Seven distinct variant hypotheses were tested:

  1. Nil Request bypass (Variant 1): Set batchParams.Request = nil to skip the req != nil check in checkNamespaceProtobuf. Result: BLOCKED on both versions — the code panics when accessing Request fields, and on v1.29.5 the workflow-level ValidateBatchOperation also blocks nil requests.

  2. Non-protobuf BatchActivity path (Variant 2): Exploit the legacy BatchActivity with BatchParams.Namespace set to a victim namespace. Result: BLOCKED on both versions — BatchActivity has always used checkNamespace(batchParams.Namespace) which validates the namespace name.

  3. Cancel operation type (Variant 3): Use BATCH_OPERATION_TYPE_CANCEL with mismatched namespace instead of SIGNAL. Result: BYPASSED on v1.29.4, BLOCKED on v1.29.5 — the same root cause affects all operation types, and the fix covers all of them via checkNamespaceProtobuf.

  4. Case-insensitive namespace (Variant 4): Use "BOUND-NS" (different case) to try to bypass string comparison. Result: BYPASSED on v1.29.4, BLOCKED on v1.29.5checkNamespaceProtobuf uses exact string comparison, and Temporal namespace registry lookups are also case-sensitive.

  5. Reset operation type (Variant 5): Use BATCH_OPERATION_TYPE_RESET with mismatched namespace, targeting the getResetEventIDByType path. Result: BYPASSED on v1.29.4, BLOCKED on v1.29.5 — the fix's belt-and-suspenders change to use the namespace parameter in the reset path is effective.

  6. Direct startTaskProcessorProtobuf namespace injection (Variant 6): Call startTaskProcessorProtobuf directly with a forged namespace parameter. Result: Would use forged namespace — but this is not a real bypass because the function is unexported and only reachable through BatchActivityWithProtobuf, which validates first.

  7. Empty namespace string (Variant 7): Set Request.Namespace = "" with a valid NamespaceId. Result: BYPASSED on v1.29.4, BLOCKED on v1.29.5checkNamespaceProtobuf catches the empty string mismatch.

  8. Original CVE path (reference): SIGNAL with NamespaceId=boundNSID and Request.Namespace=otherNSName. Result: BYPASSED on v1.29.4, BLOCKED on v1.29.5.

Entry points tested: All variants go through BatchActivityWithProtobuf in service/worker/batcher/activities.go, which is the same entry point as the original CVE.

  • Package/Component: go.temporal.io/server/service/worker/batcher
  • Affected versions (original bug): 1.29.01.29.4, 1.30.01.30.2
  • Fixed versions: v1.29.5, v1.30.3
  • Risk level: No new risk identified on fixed versions. The fix is comprehensive.

Root Cause

The original root cause was that BatchActivityWithProtobuf validated only batchParams.NamespaceId (a UUID) via checkNamespaceID, but then forwarded batchParams.Request.Namespace (a name) to the internal frontend client. Because the internal frontend runs with NoopClaimClaimMapper → RoleAdmin, any namespace name supplied by an attacker was executed unconditionally.

The fix closes this by validating both identity forms and using the worker-bound namespace for all downstream operations. No alternate path was found that could bypass these checks.

Reproduction Steps

The variant reproduction script is vuln_variant/reproduction_steps.sh. It performs the following:

  1. Copies vuln_variant/variant_test.go into service/worker/batcher/variant_test.go.
  2. Runs go test -v -run '^TestVariant_' ./... against v1.29.4 (vulnerable) and v1.29.5 (fixed).
  3. Captures output to logs/variant_v1.29.4.log and logs/variant_v1.29.5.log.
  4. Greps for BYPASSED vs BLOCKED to determine if any variant bypassed the fixed version.

Run the script:

./vuln_variant/reproduction_steps.sh

Expected behavior:

  • On v1.29.4: several variants show BYPASSED (demonstrating the original vulnerability affects multiple operation types and edge cases)
  • On v1.29.5: all variants show BLOCKED (the fix is comprehensive)

Evidence

v1.29.4 (vulnerable) test results

Log location: logs/variant_v1.29.4.log

Key excerpts:

VARIANT3_CANCEL_BYPASSED
VARIANT4_CASE_BYPASSED
VARIANT5_RESET_BYPASSED with capturedNs=other-ns
VARIANT7_EMPTY_NS_BYPASSED
ORIGINAL_CVE_BYPASSED capturedNs=other-ns
v1.29.5 (fixed) test results

Log location: logs/variant_v1.29.5.log

Key excerpts:

VARIANT3_CANCEL_BLOCKED: activity error ... namespace mismatch
VARIANT4_CASE_BLOCKED: activity error ... namespace mismatch
VARIANT5_RESET_BLOCKED: activity error ... namespace mismatch
VARIANT7_EMPTY_NS_BLOCKED: activity error ... namespace mismatch
ORIGINAL_CVE_BLOCKED: activity error ... namespace mismatch

Recommendations / Next Steps

  1. The fix is complete: No additional code changes are required to address the reported CVE.

  2. Defense in depth: Consider adding namespace validation at the internal frontend client boundary so that even if a worker misbehaves, the internal frontend rejects cross-namespace requests.

  3. Regression testing: The regression tests added in commit 90738c6200 should be preserved in all release branches. Do not relax checkNamespaceProtobuf or remove the worker-bound namespace derivation (ns := a.namespace.String()).

  4. Periodic audit: Audit other per-namespace workers annually for similar ID-vs-name validation gaps, especially when new protobuf-based activities are added.

Additional Notes

  • Idempotency: The reproduction script is idempotent. It restores the original git ref and cleans up the copied test file on exit via a trap.
  • Test methodology: Tests are unit tests in the batcher package using the Temporal SDK's TestActivityEnvironment and gomock for the frontend client and SDK client factory.
  • No real bypass found: All 7 variant hypotheses were blocked on v1.29.5. The exit code of vuln_variant/reproduction_steps.sh is 1, indicating no bypass.

CVE-2026-5199 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:56
0:00
session startedaccounts/fireworks/models/kimi-k2p6 · cve-2026-5199-batcher · cve-2026
0:19
0:19
web search
0:19
error

Unknown error

0:19
0:19
error

Unknown error

0:40
0:40
extract_facts
no facts extracted
0:41
0:41
0:41
supportrepro
0:44
0:45
0:45
0:45
0:46
0:46
0:46
0:46
0:48
0:48
0:50
0:56
$mkdir -p external && git clone --depth=100 https://github.com/temporalio/temporal.git external/temporal
6.1s
Cloning into 'external/temporal'...
08 · How to Fix

How to Fix CVE-2026-5199

Upgrade temporal · go to 1.29.5 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-5199 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-5199

Is this the frontend gRPC signal bypass some early reports described?

No. Early advisories claimed a frontend gRPC cross-namespace signal bypass, but that vector is incorrect: the public frontend authorization interceptor correctly validates Request.Namespace against the caller's claims on both affected and fixed versions. The real vulnerability is isolated to the per-namespace batcher worker's protobuf handling, not the public frontend API.

Which Temporal versions are affected by CVE-2026-5199, and where is it fixed?

Temporal Server versions 1.29.0 - 1.29.4 (and 1.30.0 - 1.30.2) are affected. It is fixed in v1.29.5.

How severe is CVE-2026-5199?

It is rated medium severity: an authenticated attacker with writer access in one namespace can cause cross-namespace workflow mutation (signal, cancel, terminate, or reset) in a different namespace, but the vulnerability requires valid authentication and is not remotely exploitable by an unauthenticated party.

How can I reproduce CVE-2026-5199?

Download the verified script from this page and run it in an isolated environment against Temporal Server 1.29.4. It authenticates as a writer in one namespace, submits a BatchOperationInput protobuf whose NamespaceId matches the attacker's namespace but whose Request.Namespace targets a victim namespace, and confirms the batcher mutates workflows in the victim namespace on the vulnerable build while v1.29.5 rejects the mismatch.
11 · References

References for CVE-2026-5199

Authoritative sources for CVE-2026-5199 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.