{"record":{"id":"08b627fa2d752139","repo":"netbirdio/netbird","slug":"peer-has-no-stored-ssh-host-key","errorCode":null,"errorMessage":"peer has no stored SSH host key","messagePattern":"peer has no stored SSH host key","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"client/ssh/common.go","lineNumber":30,"sourceCode":"\tlog \"github.com/sirupsen/logrus\"\n\t\"golang.org/x/crypto/ssh\"\n\n\t\"github.com/netbirdio/netbird/client/proto\"\n\t\"github.com/netbirdio/netbird/util\"\n)\n\nconst (\n\tNetBirdSSHConfigFile = \"99-netbird.conf\"\n\n\tUnixSSHConfigDir    = \"/etc/ssh/ssh_config.d\"\n\tWindowsSSHConfigDir = \"ssh/ssh_config.d\"\n)\n\nvar (\n\t// ErrPeerNotFound indicates the peer was not found in the network\n\tErrPeerNotFound = errors.New(\"peer not found in network\")\n\t// ErrNoStoredKey indicates the peer has no stored SSH host key\n\tErrNoStoredKey = errors.New(\"peer has no stored SSH host key\")\n)\n\n// HostKeyVerifier provides SSH host key verification\ntype HostKeyVerifier interface {\n\tVerifySSHHostKey(peerAddress string, key []byte) error\n}\n\n// DaemonHostKeyVerifier implements HostKeyVerifier using the NetBird daemon\ntype DaemonHostKeyVerifier struct {\n\tclient proto.DaemonServiceClient\n}\n\n// NewDaemonHostKeyVerifier creates a new daemon-based host key verifier\nfunc NewDaemonHostKeyVerifier(client proto.DaemonServiceClient) *DaemonHostKeyVerifier {\n\treturn &DaemonHostKeyVerifier{\n\t\tclient: client,\n\t}\n}","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/ssh/common.go#L12-L48","documentation":"Sentinel ErrNoStoredKey (client/ssh/common.go:30) from the shared SSH package. The NetBird daemon learns each peer's SSH host key from management and stores it; the HostKeyVerifier used by the ssh client returns this sentinel when the peer is known in the overlay but no host key has been stored for it yet, so strict host verification cannot proceed.","triggerScenarios":"DaemonHostKeyVerifier.VerifySSHHostKey is called for a peer whose key the engine never received: SSH server feature not enabled on the remote peer, the peer's key not yet distributed via the network map, or lookup done before the engine synced (the underlying GetPeerSSHHostKey path returning Found=false).","commonSituations":"First SSH attempt to a peer whose owner has not enabled netbird's SSH server; connecting right after either side connected before the network map converged; environments where management disables SSH key distribution.","solutions":["Enable the NetBird SSH server on the target peer (management setting / peer config) so its host key is published.","Wait for both peers to be Connected and the network map to sync, then retry.","In interactive clients, distinguish this sentinel (ErrPeerNotFound vs ErrNoStoredKey) and prompt the user instead of failing silently; avoid falling back to accepting an unverified key automatically."],"exampleFix":"// before: any verification error aborts with a generic message\nif err := verifier.VerifySSHHostKey(addr, key); err != nil {\n    return fmt.Errorf(\"ssh host key verification failed: %w\", err)\n}\n\n// after: distinguish 'no key stored' and guide the user\nif err := verifier.VerifySSHHostKey(addr, key); err != nil {\n    if errors.Is(err, ssh.ErrNoStoredKey) {\n        return fmt.Errorf(\"no stored host key for %s; is the peer's SSH server enabled?\", addr)\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// Ask the daemon first whether a key exists before strict verification.\nresp, err := daemonClient.GetPeerSSHHostKey(ctx, &daemonpb.GetPeerSSHHostKeyRequest{PeerAddress: addr})\nif err == nil && !resp.GetFound() {\n    return fmt.Errorf(\"no stored host key for %s; enable SSH on the peer or wait for sync\", addr)\n}","typeGuard":"func isNoStoredKey(err error) bool {\n    return errors.Is(err, ssh.ErrNoStoredKey)\n}","tryCatchPattern":"if err := verifier.VerifySSHHostKey(addr, key); err != nil {\n    if ssh.IsNoStoredKey(err) {\n        // trust not yet established: prompt the user or wait for network-map sync\n        return fmt.Errorf(\"no stored key for peer %s\", addr)\n    }\n    return err // genuine key mismatch: abort\n}","preventionTips":["Distinguish ErrNoStoredKey from a key mismatch: one is 'unknown', the other is 'suspected MITM'.","Never auto-accept an unverifiable key when none is stored.","Ensure peers run with the SSH server feature enabled so keys get distributed."],"tags":["go","netbird","ssh","host-key","trust-on-first-use"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}