juanfont/headscale · error · ErrSSHTestEmptySrc

SSH tests entry must have a non-empty src

Error message

SSH tests entry must have a non-empty src

What it means

ErrSSHTestEmptySrc is returned by validateSSHTests (hscontrol/policy/v2/types.go:3283) when an sshTests entry has no usable "src" (nil Alias). Every SSH test must name who is attempting the connection. The src field unmarshals into an *Alias so a whitespace-only or missing value surfaces here rather than as a parser error (see test.go:103).

Source

Thrown at hscontrol/policy/v2/types.go:152

	ErrAutogroupNotSupported       = errors.New("autogroup not supported in headscale")
	ErrAutogroupInternetSrc        = errors.New("autogroup:internet can only be used in ACL destinations")
	ErrAutogroupSelfSrc            = errors.New("\"autogroup:self\" not valid on the src side of a rule")
	ErrAutogroupNotSupportedACLSrc = errors.New("autogroup not supported for ACL sources")
	ErrAutogroupNotSupportedACLDst = errors.New("autogroup not supported for ACL destinations")
	ErrAutogroupDangerAllDst       = errors.New("cannot use autogroup:danger-all as a dst")
	ErrAutogroupNotSupportedSSHSrc = errors.New("autogroup not supported for SSH sources")
	ErrAutogroupNotSupportedSSHDst = errors.New("autogroup not supported for SSH destinations")
	ErrHostNotDefined              = errors.New("host not defined in policy")
	ErrSSHSourceAliasNotSupported  = errors.New("alias not supported for SSH source")
	ErrSSHDestAliasNotSupported    = errors.New("alias not supported for SSH destination")
	ErrUnknownField                = errors.New("unknown field")
	ErrProtocolNoSpecificPorts     = errors.New("protocol does not support specific ports")
	ErrTestEmptyAssertions         = errors.New("test entry must have at least one of \"accept\" or \"deny\"")
	ErrTestProtocolNotAllowed      = errors.New("test protocol must be tcp, udp, sctp, or empty")
	ErrTestDestinationMultiPort    = errors.New("test destination port must be a single port")
	ErrTestDestinationCIDR         = errors.New("test destination must be a single host, not a CIDR range")
	ErrAutogroupInternetTestDst    = errors.New("autogroup:internet not valid as a test destination")
	ErrSSHTestEmptySrc             = errors.New("SSH tests entry must have a non-empty src")
	ErrSSHTestEmptyDst             = errors.New("SSH tests entry must have at least one dst")
	ErrSSHTestDstUnknownTag        = errors.New("SSH tests dst contains unknown tag")
	ErrSSHTestDstDisallowedElement = errors.New("SSH tests dst contains disallowed element")
)

type resolved struct {
	ips netipx.IPSet
}

func newResolved(ipb *netipx.IPSetBuilder) (resolved, error) {
	ips, err := ipb.IPSet()
	if err != nil {
		return resolved{}, err
	}

	return resolved{ips: *ips}, nil
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Add a src to the flagged sshTests entry — a user (user:*, user:name), group, tag, or autogroup valid for SSH sources
  2. If the entry was scaffolding, delete it
  3. Re-validate; remaining sshTests shape errors (empty dst, bad dst) are reported together

Example fix

// before
"sshTests": [{"dst": ["tag:server"], "accept": ["root"]}]
// after
"sshTests": [{"src": "group:admin", "dst": ["tag:server"], "accept": ["root"]}]
Defensive patterns

Strategy: validation

Validate before calling

// Before marshalling a policy, ensure every sshTests entry has src set
func sshTestEntriesValid(tests []SSHPolicyTest) bool {
    for _, t := range tests {
        if t.Src == nil { return false }
    }
    return true
}

Try / catch

if errors.Is(err, policyv2.ErrSSHTestEmptySrc) {
    // add a user/group/tag/autogroup src to the flagged entry (index in message)
}

Prevention

When it happens

Trigger: An sshTests entry like {"dst": ["tag:server"], "accept": ["root"]} with src omitted, or src set to an empty string that collapses to nil. Raised when t.Src == nil during policy validation.

Common situations: Scaffolding an ssh test and forgetting the user side; trimming/copy-paste dropping the src line; assuming src is optional like proto in packet tests (it is not).

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/8845996dcfd36911. Report an issue: GitHub.