critical · 9.1CVE-2026-39832Jun 25, 2026

CVE-2026-39832: golang.org/x/crypto/ssh/agent SSH Key Forwarding Constraint Bypass

Shubham Kandhare
Security Engagement Manager, SecureLayer7

golang.org/x/crypto/ssh/agent < 0.52.0 silently drops restrict-destination constraint extensions when forwarding SSH keys to a remote agent, enabling lateral movement.

Packagegolang.org/x/crypto/ssh/agent
Ecosystemgo
Affected< 0.52.0
Fixed in0.52.0
CVE-2026-39832: golang.org/x/crypto/ssh/agent SSH Key Forwarding Constraint Bypass

The problem

In golang.org/x/crypto/ssh/agent before v0.52.0, the client-side AddedKey serialization routine omitted all ConstraintExtension fields (e.g., restrict-destination-v00@openssh.com) from the SSH wire message when adding a key to a remote agent. The remote agent stored the key with no destination restrictions, so any attacker with access to the remote agent socket could use the forwarded key to authenticate to any host the key is valid for, not just the intended destination chain.

Additionally, NewKeyring() silently accepted keys with unsupported constraint extensions instead of returning an error.

Proof of concept

go
// Victim forwards a key with destination restriction via a Go SSH agent client.
// On a vulnerable client (< v0.52.0), the ConstraintExtensions field is
// entirely absent from the SSH_AGENTC_ADD_ID_CONSTRAINED wire message.
// The remote agent receives and stores the key as if no extension constraints
// were ever set — indistinguishable from an unconstrained key add.

err := remoteAgent.Add(agent.AddedKey{
    PrivateKey: privateKey,
    Comment:    "my-restricted-key",
    ConstraintExtensions: []agent.ConstraintExtension{
        {
            // This extension is silently dropped pre-patch; never reaches remote agent.
            ExtensionName:    "restrict-destination-v00@openssh.com",
            ExtensionDetails: destinationConstraintBlob,
        },
    },
})
// Post-forward, attacker on the remote host uses ssh-add -L and
// the unrestricted forwarded key to pivot to any reachable host:
// $ ssh -A attacker@pivot-host
// $ ssh target-host  # succeeds; no destination restriction enforced

The root cause is a missing serialization loop in the buildConstraints helper inside ssh/agent/client.go. Pre-patch, the function appended SSH_AGENT_CONSTRAIN_LIFETIME (0x01) and SSH_AGENT_CONSTRAIN_CONFIRM (0x02) bytes for standard constraints, but never iterated over AddedKey.ConstraintExtensions to emit SSH_AGENT_CONSTRAIN_EXTENSION (0xFF) type-length-value blocks.

Because constraint extension bytes were never written to the wire message, the remote OpenSSH agent received a plain SSH_AGENTC_ADD_ID_CONSTRAINED request with zero extension constraints, effectively identical to adding an unrestricted key. The patch adds the missing loop that serializes each ConstraintExtension as a 0xFF-prefixed name+details blob, and separately makes NewKeyring() return an error on unsupported extensions rather than silently ignoring them (CWE-502 / improper deserialization of security-critical fields).

No public PoC exists; payload derived from advisory and patch diff analysis.

The fix

Upgrade golang.org/x/crypto to v0.52.0 and rebuild all dependent binaries. Run govulncheck ./... to identify affected call sites. Until patched, disable SSH agent forwarding on sessions using destination-restricted keys (AllowAgentForwarding no on intermediate hosts) and use the native OpenSSH client for restrict-destination workflows.

Rotate any SSH keys forwarded through vulnerable Go clients to untrusted hosts.

Reported by NCC Group Cryptography Services (sponsored by Teleport).

References: [1][2][3][4][5][6]