Jun 26, 2026·7 min read·2 visits
An issue in the Go SSH agent client silently drops custom key constraints during serialization, allowing attackers on a compromised jump host to reuse forwarded keys without restrictions and bypass security boundaries.
A critical security flaw was identified in the Go package golang.org/x/crypto/ssh/agent. The vulnerability arises during the serialization of key constraints when adding SSH identities to a remote agent or an in-memory keyring. Specifically, custom constraint extensions, such as destination restrictions like restrict-destination-v00@openssh.com, were silently omitted from serialization in client requests. This omission allowed keys to be loaded into the remote agent with zero destination-based restrictions, enabling unauthorized users with access to the agent socket on intermediate hosts to authenticate to any downstream host without policy enforcement. The issue was resolved in version v0.52.0 of the golang.org/x/crypto library.
The golang.org/x/crypto/ssh/agent package provides Go implementations of the SSH agent client and an in-memory SSH keyring. Developers utilize this library to manage SSH keys, establish connections, and handle key forwarding programmatically. The package serves as a critical component in custom SSH clients, deployment orchestrators, and system management tools that interact with remote infrastructure.\n\nUnder standard operational parameters, SSH key forwarding allows an intermediate system, such as a jump host or bastion host, to utilize the user's local SSH agent to authenticate subsequent connections. To protect against intermediate server compromise, the OpenSSH agent protocol introduces key constraints. These constraints restrict when, where, and how a forwarded identity can be utilized.\n\nThe vulnerability, identified as CVE-2026-39832, represents a breakdown in the enforcement of these security boundaries. The Go SSH agent client silently dropped modern key constraints, specifically those involving destination restrictions, during network serialization. Consequently, keys intended for highly constrained, single-hop usage were loaded into remote agents without any protective boundaries, exposing downstream environments to unauthorized lateral movement.
The root cause of CVE-2026-39832 lies in the client-side serialization routine within Go's SSH agent implementation. When a Go client adds an identity to an agent via the SSH_AGENTC_ADD_ID_CONSTRAINED request, it must serialize all associated constraints into a binary stream. The standard constraints include parameters such as lifetime limitations and explicit user confirmation prompts.\n\nModern versions of the OpenSSH agent protocol implement a generic constraint extension mechanism, denoted by the constraint type SSH_AGENT_CONSTRAINT_EXTENSION (byte value 255). This mechanism enables the transmission of arbitrary, named extensions, such as restrict-destination-v00@openssh.com. This specific extension restricts signature requests to designated hosts and users, mitigating risks associated with compromised intermediate hops.\n\nIn vulnerable versions of the golang.org/x/crypto library, the key serialization logic did not contain the switch cases or handlers required to recognize the generic SSH_AGENT_CONSTRAINT_EXTENSION type. When generating the byte payload, the client parsed standard constraint structures but skipped any element containing custom extension data. Because the serializer did not raise an error or halt execution, it generated an apparently valid network packet that omitted the constraints entirely.\n\nAdditionally, the in-memory keyring implementation provided by agent.NewKeyring() exhibited a complementary flaw. When applications loaded keys with custom constraint extensions into the keyring, the keyring.Add method silently ignored the unsupported extension fields rather than raising an error. This behavior compounded the severity of the flaw, creating a systemic failure to enforce security boundaries across both client-server and in-memory contexts.
To understand the technical manifestation of the bug, we examine how the constraints were parsed and serialized into the network packet. Prior to the fix, the serialization loop in the client only processed standard, legacy constraints. Below is a representation of the vulnerable code pattern where the extension type is not accounted for:\n\ngo\n// VULNERABLE: Only standard constraints are checked\nfunc marshalConstraints(constraints []agent.Constraint) []byte {\n var out []byte\n for _, c := range constraints {\n switch c.Type {\n case agent.SSH_AGENT_CONSTR_LIFETIME:\n out = append(out, agent.SSH_AGENT_CONSTR_LIFETIME)\n out = appendUint32(out, c.Lifetime)\n case agent.SSH_AGENT_CONSTR_CONFIRM:\n out = append(out, agent.SSH_AGENT_CONSTR_CONFIRM)\n // Missing case for SSH_AGENT_CONSTRAINT_EXTENSION (255)\n // Custom extensions like restrict-destination are silently ignored\n }\n } return out\n}\n\n\nThe corresponding patch introduced proper handling for custom extension types. It ensures that any constraint of type SSH_AGENT_CONSTRAINT_EXTENSION is serialized using its name and string payload format. The updated serialization logic behaves as follows:\n\ngo\n// PATCHED: Support for extension constraints added\nfunc marshalConstraints(constraints []agent.Constraint) []byte {\n var out []byte\n for _, c := range constraints {\n switch c.Type {\n case agent.SSH_AGENT_CONSTR_LIFETIME:\n out = append(out, agent.SSH_AGENT_CONSTR_LIFETIME)\n out = appendUint32(out, c.Lifetime)\n case agent.SSH_AGENT_CONSTR_CONFIRM:\n out = append(out, agent.SSH_AGENT_CONSTR_CONFIRM)\n case agent.SSH_AGENT_CONSTRAINT_EXTENSION: // Added in v0.52.0\n out = append(out, agent.SSH_AGENT_CONSTRAINT_EXTENSION)\n out = appendString(out, c.ExtensionName)\n out = appendString(out, c.ExtensionDetails)\n }\n } return out\n}\n\n\nFurthermore, the patch modified the keyring's behavior. The in-memory keyring now explicitly evaluates incoming constraints and rejects keys if they contain unrecognized or unsupported extensions. This ensures that if the system cannot enforce the security policy, it fails closed rather than silently downgrading the security state of the key:\n\ngo\n// PATCHED Keyring enforcement: fail-closed on unknown constraints\nif len(key.Constraints) > 0 {\n for _, c := range key.Constraints {\n if !isSupportedConstraint(c) {\n return errors.New(\"ssh: unsupported agent constraint extension\")\n }\n }\n}\n
Exploitation of CVE-2026-39832 does not require the attacker to modify network packets in transit or exploit memory safety bugs. Instead, the vulnerability relies on the inherent behavior of the client silently stripping policy definitions, which results in the deployment of an over-privileged agent session on a target host.\n\nAn attack scenario typically unfolds within an enterprise jump host environment. A systems administrator connects to a compromise-vulnerable bastion server using a custom Go-based SSH client. Because the administrator wants to restrict the risk of key hijacking, they specify a destination constraint on their SSH key, restricting usage exclusively to production database server db-prod-01.internal.\n\nmermaid\ngraph LR\n Client[\"Go SSH Client (Vulnerable)\"] -->|\"Stripped Constraints\"| Bastion[\"Bastion Server (Compromised Agent)\"]\n Attacker[\"Attacker on Bastion (Root/Session Hijack)\"] -->|\"Reads Agent Socket\"| Bastion\n Attacker -->|\"Unrestricted Auth\"| ProdServer[\"Downstream Production Server (Any Destination)\"]\n\n\nWhen the client transmits the key during connection setup, the destination restrictions are completely excluded from the payload. The SSH agent running on the bastion host registers the key as an unrestricted identity. If an attacker has compromised the bastion host (either through root escalation or by gaining access to the user's local socket path), they can intercept the active agent socket.\n\nBecause the constraint is absent, the attacker can leverage the active session to connect to any other accessible server (e.g., internal-billing-01.internal or domain-controller.internal) that trusts the administrator's key. The agent on the bastion host processes the signature requests without checking the destination, executing the authentication successfully and allowing lateral movement across the internal network.
The impact of CVE-2026-39832 is designated as Critical, with a CVSS v3.1 base score of 9.1. The primary consequence is the total defeat of destination-based access controls within forwarded SSH agent sessions. Organizations that design their security architectures around the assumption that jump hosts cannot abuse forwarded credentials are majorly exposed to lateral movement risks.\n\nFrom a technical perspective, confidentiality and integrity are heavily impacted. An attacker who gains control of a jump host can capture the cryptographic authority of any user currently connected through a vulnerable Go client. This allows the attacker to execute remote commands, extract sensitive databases, and modify security configurations on any server that trusts the user's SSH key, regardless of any policy restrictions the user attempted to enforce.\n\nDespite the severity of this design flaw, the vulnerability requires a pre-existing presence on the intermediate host or access to the agent socket. The lack of active exploitation in public databases, combined with an EPSS score of 0.00397, suggests that mass automated exploitation is unlikely. However, targeted post-exploitation scenarios within enterprise networks represent a significant threat vector.
The definitive remediation for CVE-2026-39832 is upgrading the golang.org/x/crypto dependency to version v0.52.0 or later. This upgrade ensures that custom agent extensions are serialized properly and that the in-memory keyring rejects keys with un-enforceable constraints.\n\nDevelopers should verify their codebase dependencies using the Go vulnerability scanner, govulncheck. This tool statically analyzes binaries and source code to identify calls to the affected symbols, such as client.Add and keyring.Add. To perform this validation, run the following command in the project root:\n\nbash\ngovulncheck ./...\n\n\nIf the upgrade cannot be performed immediately, several temporary workarounds can mitigate the risk. First, disable agent forwarding entirely in configuration files if it is not strictly necessary. Second, limit user access to intermediate jump hosts and audit socket permissions to prevent unauthorized processes from reading SSH agent sockets. Finally, utilize standard OpenSSH client binaries for operations requiring strict destination-based access controls, as they are unaffected by this serialization omission.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
golang.org/x/crypto Go | < 0.52.0 | 0.52.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-281 |
| Attack Vector | Network |
| CVSS v3.1 Score | 9.1 |
| EPSS Score | 0.00397 (Percentile: 31.59%) |
| Impact | Unrestricted lateral movement via forwarded SSH keys |
| Exploit Status | Proof of Concept |
| CISA KEV Status | Not Listed |
The product does not preserve the original permissions of an object when transmitting or copying it, resulting in the object being accessible with wider privileges than intended.
A high-severity Denial of Service (DoS) vulnerability (CVE-2026-46597 / GO-2026-5013) exists in the golang.org/x/crypto/ssh module before version v0.52.0. The flaw stems from an incorrect operator order during a type conversion of the GCM packet padding size, allowing a remote, unauthenticated attacker to trigger an out-of-bounds slice runtime panic and crash the Go process.
A critical security bypass vulnerability was discovered in the Go SSH server implementation within the golang.org/x/crypto/ssh package. When an SSH server authentication callback returned a PartialSuccessError alongside non-nil Permissions, the server silently discarded these permissions before the subsequent authentication step. Consequently, once the user completed the second-factor authentication, the session-level restrictions were dropped, granting the client unauthorized capabilities.
A Denial of Service (DoS) vulnerability exists in the Go SSH implementation package (golang.org/x/crypto/ssh). The vulnerability is caused by a null pointer dereference (runtime panic) when CertChecker is utilized as a public key callback but its validation fields, IsUserAuthority or IsHostAuthority, are uninitialized.
An unbounded memory leak vulnerability in the Go SSH package (golang.org/x/crypto/ssh) allows authenticated users to crash the server by repeatedly requesting connection channels that are rejected, leading to system resource exhaustion.
A denial-of-service (DoS) and resource leak vulnerability in the Go SSH package (golang.org/x/crypto/ssh) allows a malicious peer to permanently deadlock connection processing loops and leak memory. This issue stems from improper handling of unsolicited responses at the global and channel layers, which saturate internal bounded channel buffers and block the main multiplexer loop. The vulnerability is fully resolved in version 0.52.0.
A high-severity Denial of Service (DoS) vulnerability exists in the golang.org/x/crypto/ssh package prior to version 0.52.0. The vulnerability is caused by a lack of size and range validation on incoming RSA and DSA public key parameters during SSH authentication. An unauthenticated attacker can submit a crafted public key with pathologically large parameters, triggering intensive CPU computation during signature verification and leading to a complete Denial of Service.