juanfont/headscale · error
tags in SSH source cannot access user-owned devices
Error message
tags in SSH source cannot access user-owned devices
What it means
SSH validation error (hscontrol/policy/v2/types.go:2176): an SSH rule's src contains a tag (or autogroup:tagged) while its dst names user-owned devices. Tags denote shared/service identity, and Tailscale's SSH model forbids tag-owned sources from reaching user-owned SSH destinations; the rule must target tagged destinations instead.
Source
Thrown at hscontrol/policy/v2/types.go:42
"tailscale.com/util/slicesx"
)
// Global JSON options for consistent parsing across all struct unmarshaling.
var policyJSONOpts = []json.Options{
json.DefaultOptionsV2(),
json.MatchCaseInsensitiveNames(true),
json.RejectUnknownMembers(true),
}
const Wildcard = Asterix(0)
var ErrAutogroupSelfRequiresPerNodeResolution = errors.New("autogroup:self requires per-node resolution and cannot be resolved in this context")
var ErrUndefinedTagReference = errors.New("references undefined tag")
// SSH validation errors.
var (
ErrSSHTagSourceToUserDest = errors.New("tags in SSH source cannot access user-owned devices")
ErrSSHUserDestRequiresSameUser = errors.New("user destination requires source to contain only that same user")
ErrSSHAutogroupSelfRequiresUserSource = errors.New("autogroup:self destination requires source to contain only users or groups, not tags or autogroup:tagged")
ErrSSHTagSourceToAutogroupMember = errors.New("tags in SSH source cannot access autogroup:member (user-owned devices)")
ErrSSHWildcardDestination = errors.New("wildcard (*) is not supported as SSH destination")
ErrSSHCheckPeriodAboveMax = errors.New("is above the max (168h)")
ErrSSHCheckPeriodNegative = errors.New("must be a positive duration")
ErrSSHCheckPeriodOnNonCheck = errors.New("checkPeriod is only valid with action \"check\"")
ErrInvalidLocalpart = errors.New("invalid localpart format, must be localpart:*@<domain>")
ErrSSHUsersMustBeSpecified = errors.New("users must be specified")
ErrSSHUserInvalid = errors.New("is not valid")
ErrSSHAcceptEnvEmpty = errors.New("acceptEnv values cannot be empty")
ErrSSHActionMustBeSpecified = errors.New("action must be specified")
ErrSSHActionInvalid = errors.New("is not a valid action")
ErrSSHDestinationHostAlias = errors.New("invalid dst")
ErrTagNameMustStartWithLetter = errors.New("tag names must start with a letter, after 'tag:'")
ErrGroupMembersCannotBeRecursive = errors.New("group members cannot be recursive")
)
View on GitHub (pinned to 565fd254d0)
Solutions
- Point the rule at tagged destinations: dst ["autogroup:tagged"] or specific tags
- If a human must be on the source side, use users/groups in src instead of the tag
- Model tag-to-user access at the network layer (grants) rather than SSH rules
Example fix
// before
{"src": ["tag:ci"], "dst": ["user:alice"], "users": ["root"], "action": "accept"}
// after
{"src": ["tag:ci"], "dst": ["tag:server"], "users": ["deploy"], "action": "accept"} Defensive patterns
Strategy: validation
Validate before calling
func sshSrcTagsAllowed(sources, dests []string) bool { dstUserOwned := slices.ContainsFunc(dests, func(d string) bool { return !strings.HasPrefix(d, "tag:") && d != "autogroup:tagged" }); if !dstUserOwned { return true }; return !slices.ContainsFunc(sources, func(s string) bool { return strings.HasPrefix(s, "tag:") || s == "autogroup:tagged" }) } Prevention
- Keep SSH rules tag-to-tag or user-to-user
- Model tag-to-user access outside SSH rules
- Lint policies for mixed tag-src/user-dst SSH rules
When it happens
Trigger: "ssh": [{"src": ["tag:ci"], "dst": ["user:alice"], "users": ["root"], "action": "accept"}] — any tag in src with a user (or autogroup:member) dst fails validation with this error naming the offending alias.
Common situations: Trying to let a CI/automation tagged node SSH into employees' machines; converting a wildcard SSH rule to explicit entries and mixing tags with user dsts.
Related errors
- user destination requires source to contain only that same u
- autogroup:self destination requires source to contain only u
- tags in SSH source cannot access autogroup:member (user-owne
- wildcard (*) is not supported as SSH destination
- is above the max (168h)
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/129730337855a1ad.
Report an issue: GitHub.