From 4064f13bda2929aeff6f9b8ac5d3f1b8dd5fb946 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 8 Apr 2026 12:25:50 +0000 Subject: [PATCH] types: fix nil panics in Owner() and TailscaleUserID() for orphaned nodes Owner() on a non-tagged node with nil User returns an invalid UserView that panics when Name() is called. Add a guard to return an empty UserView{} when the user is not valid. TailscaleUserID() calls UserID().Get() without checking Valid() first, which panics on orphaned nodes (no tags, no UserID). Add a validity check to return 0 for this invalid state. Callers should check Owner().Valid() before accessing fields. --- hscontrol/types/node.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/hscontrol/types/node.go b/hscontrol/types/node.go index ed92aff5..c23c56d1 100644 --- a/hscontrol/types/node.go +++ b/hscontrol/types/node.go @@ -789,12 +789,18 @@ func (nv NodeView) MarshalZerologObject(e *zerolog.Event) { // Owner returns the owner for display purposes. // For tagged nodes, returns TaggedDevices. For user-owned nodes, returns the user. +// Returns an invalid UserView if the node is in an orphaned state (no tags, no user). +// Callers should check .Valid() on the result before accessing fields. func (nv NodeView) Owner() UserView { if nv.IsTagged() { return TaggedDevices.View() } - return nv.User() + if user := nv.User(); user.Valid() { + return user + } + + return UserView{} } func (nv NodeView) IPs() []netip.Addr { @@ -996,6 +1002,7 @@ func (nv NodeView) TypedUserID() UserID { // TailscaleUserID returns the user ID to use in Tailscale protocol. // Tagged nodes always return TaggedDevices.ID, user-owned nodes return their actual UserID. +// Returns 0 for nodes in an orphaned state (no tags, no UserID). func (nv NodeView) TailscaleUserID() tailcfg.UserID { if !nv.Valid() { return 0 @@ -1006,6 +1013,10 @@ func (nv NodeView) TailscaleUserID() tailcfg.UserID { return tailcfg.UserID(int64(TaggedDevices.ID)) } + if !nv.UserID().Valid() { + return 0 + } + //nolint:gosec // G115: UserID values are within int64 range return tailcfg.UserID(int64(nv.UserID().Get())) }