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.
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).
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 — limited impact or hard to exploit. Address in the normal cycle.
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 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 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.
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
Root Cause and Exploit Chain for CVE-2026-5199
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— specificallyBatchActivityWithProtobufandstartTaskProcessorProtobuf - Affected versions:
1.29.0–1.29.4and1.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:
BatchActivityWithProtobufreceives aBatchOperationInputprotobuf.- It calls
checkNamespaceID(batchParams.NamespaceId)which only verifies the namespace UUID matches the worker-bound namespace ID. - It then uses
batchParams.Request.Namespacefor downstream operations:config.namespace = batchParams.Request.NamespacestartTaskProcessorProtobuf(..., batchParams.Request.Namespace, ...)
startTaskProcessorProtobufpasses this attacker-controlled namespace name to all internal frontend client calls (SignalWorkflowExecution,CancelWorkflowExecution,TerminateWorkflowExecution,ResetWorkflowExecution, etc.).- 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 IDbatchParams.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:
- Clones the
temporalio/temporalrepository (or uses an existing clone). - Builds the
temporal-serverbinary from source for bothv1.29.4(vulnerable) andv1.29.5(fixed). - Starts a real Temporal Server with SQLite in-memory persistence (
development-sqlite.yaml) and--allow-no-auth. - Polls port
127.0.0.1:7233until the gRPC frontend is listening. - Runs a standalone Go program (
repro_main.go) that:- Connects to the real server via gRPC
- Creates two namespaces:
attacker-nsandvictim-ns - Starts a victim workflow in
victim-ns - Gets the namespace ID for
attacker-ns - Constructs a forged
BatchOperationInputwhereNamespaceIdmatchesattacker-nsbutRequest.Namespaceisvictim-ns - Directly starts the internal
temporal-sys-batch-workflow-protobufworkflow inattacker-nswith this forged payload - Waits 15 seconds for the per-namespace batcher worker to execute
- Queries the victim workflow history for
WORKFLOW_EXECUTION_SIGNALEDevents
- On v1.29.4: the victim workflow history contains a signal event, proving the cross-namespace bypass.
- 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.loglogs/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.5or later (orv1.30.3/v1.31.0depending on the release line). - Verify that the
checkNamespaceProtobufvalidation exists in any custom builds or forks. - Testing: Add regression tests that verify both
NamespaceIdandRequest.Namespacemismatches 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
SIGNALbatch type, but the same bypass applies toCANCEL,TERMINATE,RESET,DELETE,UPDATE_EXECUTION_OPTIONS,UNPAUSE_ACTIVITY,RESET_ACTIVITY, andUPDATE_ACTIVITY_OPTIONSbecause all paths instartTaskProcessorProtobufuse the same attacker-controllednamespaceparameter. - 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
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:
- Both namespace identity forms must match:
batchParams.NamespaceId(UUID) andbatchParams.Request.Namespace(name) must both equal the worker's bound namespace. - The worker-bound namespace is authoritative: After validation,
ns := a.namespace.String()is used for all SDK client creation andstartTaskProcessorProtobufcalls. - 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:
BatchActivityWithProtobufactivity entry point- All batch operation types (SIGNAL, CANCEL, TERMINATE, RESET, DELETE, UPDATE_EXECUTION_OPTIONS, UNPAUSE_ACTIVITY, RESET_ACTIVITY, UPDATE_ACTIVITY_OPTIONS)
- The
BatchWorkflowProtobufworkflow that invokes the activity
The fix does NOT cover:
- Direct invocation of
startTaskProcessorProtobufwith a forged namespace parameter (but this function is unexported and unreachable without first bypassingBatchActivityWithProtobuf) - 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:
Nil Request bypass (Variant 1): Set
batchParams.Request = nilto skip thereq != nilcheck incheckNamespaceProtobuf. Result: BLOCKED on both versions — the code panics when accessingRequestfields, and on v1.29.5 the workflow-levelValidateBatchOperationalso blocks nil requests.Non-protobuf BatchActivity path (Variant 2): Exploit the legacy
BatchActivitywithBatchParams.Namespaceset to a victim namespace. Result: BLOCKED on both versions —BatchActivityhas always usedcheckNamespace(batchParams.Namespace)which validates the namespace name.Cancel operation type (Variant 3): Use
BATCH_OPERATION_TYPE_CANCELwith 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 viacheckNamespaceProtobuf.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.5 —checkNamespaceProtobufuses exact string comparison, and Temporal namespace registry lookups are also case-sensitive.Reset operation type (Variant 5): Use
BATCH_OPERATION_TYPE_RESETwith mismatched namespace, targeting thegetResetEventIDByTypepath. Result: BYPASSED on v1.29.4, BLOCKED on v1.29.5 — the fix's belt-and-suspenders change to use thenamespaceparameter in the reset path is effective.Direct
startTaskProcessorProtobufnamespace injection (Variant 6): CallstartTaskProcessorProtobufdirectly with a forgednamespaceparameter. Result: Would use forged namespace — but this is not a real bypass because the function is unexported and only reachable throughBatchActivityWithProtobuf, which validates first.Empty namespace string (Variant 7): Set
Request.Namespace = ""with a validNamespaceId. Result: BYPASSED on v1.29.4, BLOCKED on v1.29.5 —checkNamespaceProtobufcatches the empty string mismatch.Original CVE path (reference): SIGNAL with
NamespaceId=boundNSIDandRequest.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.0–1.29.4,1.30.0–1.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:
- Copies
vuln_variant/variant_test.gointoservice/worker/batcher/variant_test.go. - Runs
go test -v -run '^TestVariant_' ./...againstv1.29.4(vulnerable) andv1.29.5(fixed). - Captures output to
logs/variant_v1.29.4.logandlogs/variant_v1.29.5.log. - Greps for
BYPASSEDvsBLOCKEDto 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
The fix is complete: No additional code changes are required to address the reported CVE.
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.
Regression testing: The regression tests added in commit
90738c6200should be preserved in all release branches. Do not relaxcheckNamespaceProtobufor remove the worker-bound namespace derivation (ns := a.namespace.String()).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
batcherpackage using the Temporal SDK'sTestActivityEnvironmentand 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.shis 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.
Unknown error
Unknown error
mkdir -p external && git clone --depth=100 https://github.com/temporalio/temporal.git external/temporalCloning into 'external/temporal'...
Artifacts and Evidence for CVE-2026-5199
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-5199
Upgrade temporal · go to 1.29.5 or later.
FAQ: CVE-2026-5199
Is this the frontend gRPC signal bypass some early reports described?
Which Temporal versions are affected by CVE-2026-5199, and where is it fixed?
How severe is CVE-2026-5199?
How can I reproduce CVE-2026-5199?
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.