From 3f573ef2938c2cdbcf34ad4ac6baaeb99c1d6e4e Mon Sep 17 00:00:00 2001 From: Roland Urbano Date: Sat, 23 May 2026 22:26:05 +0200 Subject: [PATCH] fix(config): error on ambiguous peer ID prefix (#113) FindPeerByIDPrefix previously returned the first peer whose ID started with the given prefix, silently ignoring further matches. With Ed25519 peer IDs all sharing the leading "12D3KooW" (8 chars), short prefixes can match multiple peers; the chosen peer then depends on slice order, which is effectively undefined from the user's perspective. Change FindPeerByIDPrefix and its wrapper FindPeerByCLIRef to return (*Peer, error): - 0 matches -> (nil, nil) - 1 match -> (&peer, nil) - 2+ match -> (nil, error) describing the ambiguity Call sites in config.Read and rpc/server.go updated accordingly: ambiguity surfaces as a config-load / CLI error instead of a silent wrong-peer resolution. Not a wire-protocol or config-format change. Only behavior visible at CLI invocation and config load. Users relying on short prefixes that happened to match multiple peers will now see a clear error and must lengthen the prefix. --- config/config.go | 44 +++++++++++++++++++++++++++++++------------- rpc/server.go | 7 +++++-- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/config/config.go b/config/config.go index b2c36b9..fbec53a 100644 --- a/config/config.go +++ b/config/config.go @@ -166,15 +166,21 @@ func Read(path string) (*Config, error) { whitelist := make(map[peer.ID]struct{}) blacklist := make(map[peer.ID]struct{}) for _, p := range service.Acl.Whitelist { - cfgPeer, found := FindPeerByCLIRef(result.Peers, p) - if !found { + cfgPeer, err := FindPeerByCLIRef(result.Peers, p) + if err != nil { + return nil, err + } + if cfgPeer == nil { return nil, errors.New("unknown peer: " + p) } whitelist[cfgPeer.ID] = struct{}{} } for _, peerStr := range service.Acl.Blacklist { - cfgPeer, found := FindPeerByCLIRef(result.Peers, peerStr) - if !found { + cfgPeer, err := FindPeerByCLIRef(result.Peers, peerStr) + if err != nil { + return nil, err + } + if cfgPeer == nil { return nil, errors.New("unknown peer: " + peerStr) } blacklist[cfgPeer.ID] = struct{}{} @@ -212,22 +218,34 @@ func FindPeerByName(peers []Peer, needle string) (*Peer, bool) { return nil, false } -func FindPeerByIDPrefix(peers []Peer, needle string) (*Peer, bool) { - for _, p := range peers { - if strings.HasPrefix(p.ID.String(), needle) { - return &p, true +// FindPeerByIDPrefix returns the unique peer whose ID starts with needle. +// Returns (nil, nil) if no peer matches. +// Returns (nil, error) if more than one peer matches (ambiguous prefix). +func FindPeerByIDPrefix(peers []Peer, needle string) (*Peer, error) { + var match *Peer + count := 0 + for i := range peers { + if strings.HasPrefix(peers[i].ID.String(), needle) { + match = &peers[i] + count++ } } - return nil, false + if count > 1 { + return nil, fmt.Errorf("ambiguous peer prefix %q matches %d peers", needle, count) + } + return match, nil } -func FindPeerByCLIRef(peers []Peer, needle string) (*Peer, bool) { +// FindPeerByCLIRef resolves a CLI peer reference: "@name" for name lookup, +// otherwise a peer ID prefix. Returns (nil, nil) on no match, +// (nil, error) only on ambiguous ID prefix. +func FindPeerByCLIRef(peers []Peer, needle string) (*Peer, error) { if strings.HasPrefix(needle, "@") { name := strings.TrimPrefix(needle, "@") - return FindPeerByName(peers, name) - } else { - return FindPeerByIDPrefix(peers, needle) + p, _ := FindPeerByName(peers, name) + return p, nil } + return FindPeerByIDPrefix(peers, needle) } func (cfg Config) FindRoute(needle net.IPNet) (*RouteTableEntry, bool) { diff --git a/rpc/server.go b/rpc/server.go index b824fb8..2531985 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -120,8 +120,11 @@ func (hsr *HyprspaceRPC) Route(args *RouteArgs, reply *RouteReply) error { if err != nil { return err } - target, found := config.FindPeerByCLIRef(hsr.config.Peers, args.Args[1]) - if !found { + target, err := config.FindPeerByCLIRef(hsr.config.Peers, args.Args[1]) + if err != nil { + return err + } + if target == nil { return errors.New("no such peer") } err = hsr.tunDev.Apply(tun.Route(*network))