CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-61687

CVE-2026-61687: OAuth State Validation Bypass and Login CSRF in Hatchet

Amit Schendel
Amit Schendel
Senior Security Researcher

Sep 21, 2026·10 min read·40 visits

Executive Summary (TL;DR)

A session validation flaw in Hatchet allows attackers to bypass OAuth state verification using empty parameters, enabling Login CSRF and unauthorized account association.

A logic error in Hatchet's OAuth state validation mechanism allows unauthenticated remote attackers to bypass state parameter verification. By submitting an empty state parameter, attackers can exploit an equality collision with cleared session keys, facilitating Login Cross-Site Request Forgery (Login CSRF) or Session Fixation.

Vulnerability Overview

Hatchet is a distributed background task orchestration and work-dispatch platform designed for running durable workflows and managing AI agents at scale. Because it coordinates execution flows across multiple environments, secure identity management is a core structural requirement. The system integrates standard OAuth 2.0 authentication flows with popular third-party identity providers, including Google, GitHub, and Slack. These identity providers establish user authority, allowing workers and administrators to interact securely with the system management console.

The vulnerability, cataloged as CVE-2026-61687, manifests within the application's OAuth callback handler, specifically in the ValidateOAuthState helper function. The role of this function is to protect the user's login sequence against Cross-Site Request Forgery (CSRF). It does this by evaluating a cryptographically random, single-use state token that must match across the initiation and callback phases of the authentication flow. Due to an operational logic error, however, the token-cleaning phase fails to securely drop the validated state token from active memory, leaving a vacant key structure that can be targeted.

This architectural defect falls under the classification of Improper Authentication (CWE-287), Session Fixation (CWE-384), and Cross-Site Request Forgery (CWE-352). An unauthenticated network adversary can exploit this weakness by orchestrating a situation where a victim's active session is forcefully bound to the attacker's own external identity. The subsequent sections outline how this logic collision occurs, detail the vulnerable Go code patterns, and define the necessary actions required to secure affected systems.

Root Cause Analysis

The root cause of CVE-2026-61687 lies in the lifecycle management of the OAuth state validation token stored in the user's cookie-backed session. In a standard, securely implemented OAuth 2.0 flow, the application backend generates a unique random string and places it inside the user's session variables under a specific provider key (e.g., oauth_state_github). The same string is transmitted to the third-party Identity Provider (IdP) via the state query parameter during redirection. Upon successful user authentication at the IdP, the browser is redirected back to the Hatchet server callback endpoint, which must verify that the state parameter returned by the IdP matches the state parameter stored in the user's browser session.

To prevent authorization-binding replay attacks, once a state token is validated, it must immediately be destroyed. The developer intended to fulfill this requirement by modifying the session object during the final step of the validation helper. However, rather than calling a delete method to remove the state key from the session dictionary entirely, the application set the key's value to an empty string (""). This approach left the key present in the session map, associating the active key oauth_state_github with a value of "" for the remainder of the session's validity period.

This implementation creates a critical logic collision during subsequent validation checks. Go's map structure still reports that the key exists, meaning any query targeting that key will succeed. When an attacker redirects a victim to the OAuth callback URL with an empty state parameter (?state=), the callback handler fetches the parameter and queries the session store for the stored state. The application retrieves the empty string "" from the session, compares it against the empty string "" from the URL, and incorrectly concludes that the request is valid. This allows an unauthenticated attacker to inject their own authorization code into a victim's session, resulting in a successful session-binding sequence.

Code Analysis

An analysis of the vulnerable source code in api/v1/server/authn/session_helpers.go illustrates the execution path leading to the state-bypass condition. The original code checks for the existence of the state key inside the session dictionary using a comma-ok idiom. However, because the key was previously cleared by setting its value to an empty string ("") instead of deleting it, the boolean existence check ok always evaluates to true. Below is the side-by-side comparison of the vulnerable logic and the corrected logic implemented in Hatchet version 0.91.1.

// VULNERABLE CODE PATH (Pre-v0.91.1)
func (s *SessionHelpers) ValidateOAuthState(
	c echo.Context,
	integration string,
) (isValidated bool, isOAuthTriggered bool, err error) {
	stateKey := fmt.Sprintf("oauth_state_%s", integration)
 
	session, err := s.ss.Get(c.Request(), s.ss.GetName())
	if err != nil {
		return false, false, err
	}
 
	// The key is present, even if its value is "", so ok is true
	if _, ok := session.Values[stateKey]; !ok {
		return false, false, fmt.Errorf("state parameter not found in session")
	}
 
	// If c.Request().URL.Query().Get("state") is "", this evaluates "" != ""
	if c.Request().URL.Query().Get("state") != session.Values[stateKey] {
		return false, false, fmt.Errorf("state parameters do not match")
	}
 
	// ... verification continues ...
 
	// VULNERABILITY: Setting to empty string instead of deleting the key
	session.Values[stateKey] = ""
	session.Values["oauth_triggered"] = false
 
	if err := session.Save(c.Request(), c.Response()); err != nil {
		return false, false, fmt.Errorf("could not clear session")
	}
	return true, isOAuthTriggered, nil
}

To correct this vulnerability, several structural adjustments were introduced. First, the application now explicitly rejects empty state parameters incoming from the HTTP query vector. Second, the session retrieval mechanism verifies that the stored state parameter is a non-empty string. Third, the comparison logic utilizes the constant-time comparison library to mitigate timing side-channel attacks. Finally, the application calls the native delete() function on the session values map to completely remove the state tracking key from memory, preventing any subsequent validation attempts against dead state structures.

// PATCHED CODE PATH (v0.91.1)
func (s *SessionHelpers) ValidateOAuthState(
	c echo.Context,
	integration string,
) (isValidated bool, isOAuthTriggered bool, err error) {
	stateKey := fmt.Sprintf("oauth_state_%s", integration)
	provided := c.Request().URL.Query().Get("state")
	// Fix 1: Explicitly reject empty incoming state parameters
	if provided == "" {
		return false, false, fmt.Errorf("missing state parameter")
	}
 
	session, err := s.ss.Get(c.Request(), s.ss.GetName())
	if err != nil {
		return false, false, err
	}
 
	stored, ok := session.Values[stateKey].(string)
	// Fix 2: Assert key presence AND verify it is not empty
	if !ok || stored == "" {
		return false, false, fmt.Errorf("state parameter not found in session")
	}
 
	// Fix 3: Use constant-time comparison to prevent timing leaks
	if subtle.ConstantTimeCompare([]byte(provided), []byte(stored)) != 1 {
		return false, false, fmt.Errorf("state parameters do not match")
	}
 
	// ... verification continues ...
 
	// Fix 4: Physically remove keys from the map
	delete(session.Values, stateKey)
	delete(session.Values, "oauth_triggered")
 
	if err := session.Save(c.Request(), c.Response()); err != nil {
		return false, false, fmt.Errorf("could not clear session")
	}
	return true, isOAuthTriggered, nil
}

Exploitation Methodology

Exploiting CVE-2026-61687 requires specific prerequisites but low overall execution complexity. First, the target Hatchet instance must have an active OAuth login integration enabled, such as Google or GitHub. Second, the victim must have successfully initiated and completed at least one OAuth login during their current browser session. This initial successful authentication leaves the oauth_state_<integration> session variable modified and resting at the default value of "". At this stage, the victim's session is in a vulnerable, primed state.

The attacker initiates the exploit by authenticating to the same OAuth provider (e.g., GitHub) using their own attacker-controlled account. Instead of completing the redirect back to Hatchet normally, the attacker intercepts the OAuth callback request to obtain their own unique authorization code (code parameter). The attacker then constructs a malicious callback link that references the Hatchet callback URL, incorporating their own authentication code but replacing the state parameter with an empty string: /api/v1/users/github/callback?code=ATTACKER_CODE&state=.

When the victim visits the malicious link or is forced to load it via an embedded resource, their browser transmits the request along with their active Hatchet session cookie. The Hatchet server processes the callback, extracts the empty state string, and compares it against the empty string stored in the victim's session cookie. Because they match, validation succeeds. The server processes the attacker's authorization code, retrieves the attacker's identity from the IdP, and binds the attacker's identity to the victim's session context, achieving a Login CSRF compromise.

Threat & Impact Assessment

The impact of CVE-2026-61687 is high, as it allows for severe session manipulation and unauthorized account access. Unlike traditional CSRF attacks that trigger unauthorized state modifications inside a victim's account, Login CSRF binds the attacker's external identity to the victim's active application context. If the victim continues using the system while bound to the attacker's identity, any workflows created, API tokens generated, or sensitive task data processed will be written to the attacker's workspace. The attacker can then log into their own account and inspect or control these assets.

Furthermore, because Hatchet orchestrates durable background tasks and schedules workflows, this vulnerability provides an indirect path to remote code execution (RCE) or sensitive data extraction. If an attacker can successfully bind their identity to a victim's active session, they can manipulate task parameters, view internal infrastructure tokens, or configure malicious step functions that execute on the victim's self-hosted runner infrastructure. The integrity of the automated orchestrations running within the organization is completely compromised.

The CVSS v3.1 score for this vulnerability is evaluated at 7.1 (High) with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N. While the attack vector is network-based and authentication is not required, user interaction is necessary to lure the victim to the malicious redirection trigger. The compromise of integrity is ranked high because the trust boundaries within the user sessions are completely broken, allowing attackers to manipulate session context mapping.

Remediation & Hardening

The primary and recommended resolution for CVE-2026-61687 is to upgrade the Hatchet deployment to version 0.91.1 or higher. The patches introduced in this release resolve the logic error by checking for empty state parameters, comparing state variables using constant-time evaluation, and physically deleting the keys from the session map. After performing the upgrade, administrators should ideally invalidate all existing sessions, forcing users to re-authenticate and purging any legacy session structures that might still carry empty string keys.

In scenarios where immediate upgrading is not possible, organizations should implement temporary virtual patching at the routing layer. Using a reverse proxy, Web Application Firewall (WAF), or load balancer, administrators can configure rules to inspect and block requests directed at /api/v1/users/*/callback that contain empty or missing state parameters. For example, a ModSecurity rule can be implemented to identify incoming callback GET requests where the query string contains state= followed directly by an ampersand or the end of the string, dropping these connections immediately.

# Example Nginx snippet to mitigate CVE-2026-61687
location ~* ^/api/v1/users/(github|google|slack)/callback$ {
    if ($arg_state = "") {
        return 400 "Invalid State Parameter";
    }
    proxy_pass http://hatchet_backend;
}

Additionally, developers must ensure that security hygiene is maintained across all session manipulation procedures. When handling single-use parameters or state verification tokens, always prefer deleting the respective key entirely from the underlying map structures instead of assigning zero values or empty strings. Regular static analysis and implementation of integration tests, such as the one developed for Hatchet's patch, should be integrated into continuous integration pipelines to prevent regression of this vulnerability class.

Official Patches

hatchet-devFix OAuth state validation logic error in session_helpers.go

Fix Analysis (1)

Technical Appendix

CVSS Score
7.1/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:N

Affected Systems

Hatchet background task orchestration platform

Affected Versions Detail

Product
Affected Versions
Fixed Version
Hatchet
hatchet-dev
< 0.91.10.91.1
AttributeDetail
CWE IDCWE-384, CWE-352, CWE-287
Attack VectorNetwork (AV:N)
CVSS v3.1 Score7.1 (High)
Exploit Statuspoc
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1078Valid Accounts
Initial Access
T1189Drive-by Compromise
Initial Access
CWE-384
Session Fixation

The software reuse-validates session tokens or fails to properly invalidate state variables after use, enabling attackers to hijack or pre-determine a session context.

Known Exploits & Detection

GitHubIntegration test validating the empty-state comparison bypass condition in the session helpers suite

Vulnerability Timeline

Patch committed to the Hatchet repository main branch
2026-06-30
GitHub Security Advisory GHSA-phg3-3g28-wq9v published
2026-09-21
CVE-2026-61687 officially published and assigned
2026-09-21

References & Sources

  • [1]GitHub Security Advisory GHSA-phg3-3g28-wq9v
  • [2]Fix Commit f904641
  • [3]CVE-2026-61687 Record

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 1 hour ago•CVE-2026-12411
8.4

CVE-2026-12411: Cross-Guest Volume Hijack via DevLXD Device PATCH in Canonical LXD

CVE-2026-12411 is a broken access control vulnerability in the DevLXD interface of Canonical LXD versions 6.6 through 6.8. The flaw resides in the devLXDInstancePatchHandler component, which fails to verify storage volume ownership when an instance attaches a new disk device via the /dev/lxd guest socket. When the security.devlxd.management.volumes configuration option is enabled, an untrusted guest or DevLXD identity can issue a crafted HTTP PATCH request referencing another tenant's custom storage volume. The LXD host daemon subsequently mounts the target volume into the requesting guest, permitting unauthorized cross-tenant read and write operations.

Amit Schendel
Amit Schendel
1 views•10 min read
•about 2 hours ago•CVE-2026-105769
9.3

CVE-2026-105769: Azure Entra ID Token Exfiltration in Kaniko via Unanchored ACR Hostname Regex

CVE-2026-105769 (GHSA-5q7h-w9jf-cx97, GO-2026-6225) is an origin validation error and credential exfiltration vulnerability affecting Kaniko prior to version 1.25.20. The flaw stems from an unanchored regular expression in the integrated Azure Container Registry (ACR) credential helper (github.com/chrismellard/docker-credential-acr-env). When Kaniko processes a Dockerfile containing an image reference hosted on a lookalike domain containing an ACR suffix as a substring (e.g., x.azurecr.io.attacker.com), the credential helper acquires a Microsoft Entra ID access token for Azure Resource Manager and transmits it via HTTP POST to the attacker-controlled host.

Amit Schendel
Amit Schendel
4 views•11 min read
•about 3 hours ago•CVE-2026-62300
6.5

CVE-2026-62300: Uncontrolled Resource Consumption in filecoin-pin Daemon

CVE-2026-62300 is an uncontrolled resource consumption vulnerability (CWE-400) affecting the daemon mode of filecoin-pin in versions up to and including 1.1.0. The daemon implements the IPFS Pinning Service API to store content on Filecoin's Proof of Data Possession (PDP) service. Due to the absence of rate limits, concurrency caps, execution timeouts, and Directed Acyclic Graph (DAG) traversal limits, an authenticated user—or an unauthenticated user when authentication is disabled—can exhaust system resources via crafted POST /pins requests.

Amit Schendel
Amit Schendel
5 views•11 min read
•about 4 hours ago•CVE-2026-61536
7.5

CVE-2026-61536: Remote Code Execution via Unsafe Reflection in masci/banks CompletionExtension

CVE-2026-61536 is a high-severity Remote Code Execution (RCE) vulnerability in the masci/banks Python library prior to version 2.4.3. The flaw exists within the CompletionExtension component, which parses JSON-serialized Tool definitions directly from rendered Jinja template bodies inside {% completion %} blocks. When handling tool calls returned by an LLM, the library dynamically resolves the target callable using importlib.import_module() and getattr() based on an unvalidated import_path string. An attacker who can supply or modify prompt templates can inject a malicious Tool JSON object pointing to arbitrary Python functions (such as os.system), bypassing the Jinja SandboxedEnvironment introduced in version 2.4.2 and achieving arbitrary code execution when the LLM invokes the tool.

Alon Barad
Alon Barad
6 views•10 min read
•1 day ago•CVE-2026-93363
4.3

CVE-2026-93363: Improper Access Control in Payload CMS Storage Upload Route

Payload CMS contains a missing authorization vulnerability (CWE-862) in its client-side upload instructions endpoint and the `@payloadcms/storage-vercel-blob` storage adapter. Two distinct logic defects allow authenticated users lacking collection-level `create` or `update` permissions to generate signed upload tokens and upload arbitrary files directly to external cloud storage.

Alon Barad
Alon Barad
7 views•9 min read
•1 day ago•CVE-2026-55098
6.5

CVE-2026-55098: Cross-Database Authorization Bypass via DuckDB Table-Valued I/O Functions in Basekick-Labs Arc

CVE-2026-55098 (GHSA-93cm-2v4m-c56c) is an authorization bypass and sensitive information disclosure vulnerability in Basekick-Labs Arc prior to version 26.06.2. The flaw stems from an incomplete remediation of CVE-2026-47735, which failed to restrict the full suite of DuckDB table-valued filesystem I/O functions and quoted-identifier invocations. Because Arc's query parser ignores function calls during table reference extraction, authenticated users with minimal or empty permissions can invoke functions like parquet_scan(), glob(), and read_blob() to read, enumerate, and extract telemetry data across all tenant databases residing within the shared local storage root.

Alon Barad
Alon Barad
7 views•10 min read