GHSA-9q4h-f4x5-ffq8: Low severity go/github.com/hatchet-dev/hatchet vulnerability
Summary
The V1 DurableTask stream handler registers worker-supplied durable task external IDs in an in-memory callback routing map before verifying that the authenticated tenant owns the task. If the tenant-scoped ownership check fails, the handler logs the error and continues, but the map entry persists until the stream closes.
Durable callback delivery resolves the destination stream by taskexternalid only, without checking tenant identity. As a result, a tenant A worker that knows a tenant B durable task external UUID can hold its stream open and receive tenant B's durable callback result payload when the callback is delivered on the same dispatcher process.
Impact
This advisory requires an attacker to successfully guess a durable task external UUID belonging to another tenant. Durable task external IDs are generated with uuid.New() (UUIDv4) and are not enumerable across tenants, so exploitation requires prior knowledge of a target task UUID through an out-of-band channel. Thus, while the following environments are impacted, there is an extremely low probability it would be exploited.
Who is impacted. Any Hatchet deployment that hosts more than one tenant on the same instance: - Hatchet Cloud (multi-tenant SaaS) - Self-hosted Hatchet with multiple internal teams / business units sharing one instance - Any deployment where a single tenant's API token can be obtained by an attacker
Single-tenant self-hosted deployments are unaffected in practice (the "victim" and "attacker" tenants would be the same).
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/github.com/hatchet-dev/hatchetto a version that resolves this vulnerability.Fixed in 0.95.3
Event History
Frequently Asked Questions
Which deployments are exposed to cross-tenant callback delivery?
Deployments hosting more than one tenant are impacted. The callback must be delivered on the same dispatcher process as the attacker's open worker stream.
What does an attacker need to exploit this?
The attacker needs an authenticated worker for one tenant and prior knowledge of another tenant's durable task external UUID. These UUIDs are UUIDv4 and not enumerable across tenants, so guessing alone is not considered practical.
What happens after a failed ownership check?
The handler logs the ownership-check error but retains the worker-supplied task external ID in its in-memory callback routing map until the stream closes. A callback for that ID can then be routed to the attacker's stream without a tenant identity check.
Is there an operational indicator of an attempted registration?
A failed tenant-scoped ownership check causes the handler to log an error. The routing-map entry remains active only while the associated stream is open.