{"record":{"id":"f743bc9e7402aa46","repo":"OpenNHP/opennhp","slug":"peer-not-found-in-peer-pool-type-s-pubkey-s","errorCode":null,"errorMessage":"peer not found in peer pool (type=%s, pubkey=%s)","messagePattern":"peer not found in peer pool \\(type=(.+?), pubkey=(.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nhp/core/responder.go","lineNumber":504,"sourceCode":"\t\tpeerDeviceTypeName := DeviceTypeToString(peerDeviceType)\n\t\tlog.Debug(\"validatePeer: looking up %s peer pubkey=%s in peer pool\", peerDeviceTypeName, peerPkBase64)\n\n\t\tpeer = ppd.device.LookupPeer(peerPk)\n\t\tif peer == nil {\n\t\t\t// Fallback: check dynamically-registered peers (e.g., agents\n\t\t\t// registered via NHP-REG stored in SQLite).\n\t\t\tppd.device.optionMutex.Lock()\n\t\t\tfallback := ppd.device.option.PeerLookupFallback\n\t\t\tppd.device.optionMutex.Unlock()\n\t\t\tif fallback != nil && fallback(peerPk, ppd.HeaderType) {\n\t\t\t\tlog.Info(\"validatePeer: %s peer accepted via fallback, pubkey=%s\",\n\t\t\t\t\tpeerDeviceTypeName, peerPkBase64)\n\t\t\t\t// Skip expiry/address checks for fallback peers.\n\t\t\t\tgoto peerAccepted\n\t\t\t}\n\t\t\tlog.Error(\"validatePeer: %s peer not found in peer pool, pubkey=%s\",\n\t\t\t\tpeerDeviceTypeName, peerPkBase64)\n\t\t\terr = fmt.Errorf(\"peer not found in peer pool (type=%s, pubkey=%s)\", peerDeviceTypeName, peerPkBase64)\n\t\t\treturn err\n\t\t}\n\n\t\tif peer.IsExpired() {\n\t\t\tlog.Error(\"validatePeer: %s peer expired, pubkey=%s\", peerDeviceTypeName, peerPkBase64)\n\t\t\terr = fmt.Errorf(\"peer expired (type=%s, pubkey=%s)\", peerDeviceTypeName, peerPkBase64)\n\t\t\treturn err\n\t\t}\n\n\t\tif !ppd.ConnData.CheckRecvAddress(ppd.LocalInitTime, ppd.ConnData.RemoteAddr) {\n\t\t\tlog.Error(\"validatePeer: %s peer address mismatch on connection, pubkey=%s, remoteAddr=%s\",\n\t\t\t\tpeerDeviceTypeName, peerPkBase64, ppd.ConnData.RemoteAddr)\n\t\t\terr = fmt.Errorf(\"peer does not match its previous address on this connection (type=%s, pubkey=%s)\", peerDeviceTypeName, peerPkBase64)\n\t\t\treturn err\n\t\t}\n\t\tppd.ConnData.UpdateRecvAddress(ppd.LocalInitTime, ppd.ConnData.RemoteAddr)\n\t\tpeer.UpdateRecv(ppd.LocalInitTime)\n\tpeerAccepted:","sourceCodeStart":486,"sourceCodeEnd":522,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/nhp/core/responder.go#L486-L522","documentation":"validatePeer successfully decrypted the initiator's static public key from the header, but a lookup of that key in the device's peer pool found no registered peer (and no PeerLookupFallback accepted it). The device refuses to process messages from unknown public keys — this is the core Zero Trust allowlist behavior. The error carries the expected peer device type (derived from the message type, e.g. agent for KNK, AC for ART) and the base64 public key.","triggerScenarios":"An agent sends KNK/ACC without ever having been registered (no NHP_REG/OTP) or after its entry was removed; a server/AC/relay sends a message but its public key is absent from the receiver's peer table (config.toml/server.toml peers not deployed); keys rotated with --regenerate so the pool has old keys; DisableXPeerValidation is false but no PeerLookupFallback is configured on the AC/DB for dynamically registered agents.","commonSituations":"Deploying only some hosts' peer tables after generate-nhp-keys.sh; registering an agent against server A but knocking server B; SQLite-registered agent missing because PeerLookupFallback wasn't wired; peer deleted by expiry/cleanup on the receiving node.","solutions":["Add the sender's public key to the receiver's peer table (server.toml/resource.toml peer entries) and redeploy configs from deploy/config-templates.","If keys were rotated (--regenerate), redeploy peer tables to ALL nodes in lockstep.","For agents on the AC/DB, wire option.PeerLookupFallback so dynamically registered (NHP_REG) agents are accepted.","Confirm the agent actually completed registration (NHP_REG/NHP_OTP) before sending operational messages.","Check the logged pubkey against the sender's actual key file — a stale agent key file is the usual mismatch."],"exampleFix":"// before (ac etc/server.toml) — peer missing\n[[peer]]\n# only ac and relay listed, agent key absent\n// after\n[[peer]]\ndeviceType = 1 # NHP_AGENT\npubKey = \"<base64 of nhp_agent_public_key>\"","handlingStrategy":"fallback","validationCode":"pk, _ := base64.StdEncoding.DecodeString(agentPubKeyB64)\nif dev.LookupPeer(pk) == nil && dev.Option.PeerLookupFallback == nil {\n\treturn fmt.Errorf(\"peer %s is not registered on this node; register or add to peer table first\", agentPubKeyB64)\n}","typeGuard":"func peerKnown(dev *core.Device, pk []byte) bool {\n\treturn dev.LookupPeer(pk) != nil\n}","tryCatchPattern":"err := client.SendKnock(server)\nif err != nil && strings.Contains(err.Error(), \"peer not found in peer pool\") {\n\t// re-register then retry once\n\tif rerr := client.Register(); rerr == nil {\n\t\terr = client.SendKnock(server)\n\t}\n}","preventionTips":["Deploy peer tables to every node from the same key secret after any rotation.","Run generate-nhp-keys.sh without --regenerate unless a full lockstep redeploy is planned.","On AC/DB, wire PeerLookupFallback so NHP_REG-registered agents are accepted.","Verify agents complete registration before sending operational messages.","Log and alert on 'peer not found' spikes — they usually mean config drift."],"tags":["go","zero-trust","peer-pool","authentication","config"],"backgroundTag":"resource-not-found","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}