{"record":{"id":"8845996dcfd36911","repo":"juanfont/headscale","slug":"ssh-tests-entry-must-have-a-non-empty-src","errorCode":null,"errorMessage":"SSH tests entry must have a non-empty src","messagePattern":"SSH tests entry must have a non-empty src","errorType":"validation","errorClass":"ErrSSHTestEmptySrc","httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":152,"sourceCode":"\tErrAutogroupNotSupported       = errors.New(\"autogroup not supported in headscale\")\n\tErrAutogroupInternetSrc        = errors.New(\"autogroup:internet can only be used in ACL destinations\")\n\tErrAutogroupSelfSrc            = errors.New(\"\\\"autogroup:self\\\" not valid on the src side of a rule\")\n\tErrAutogroupNotSupportedACLSrc = errors.New(\"autogroup not supported for ACL sources\")\n\tErrAutogroupNotSupportedACLDst = errors.New(\"autogroup not supported for ACL destinations\")\n\tErrAutogroupDangerAllDst       = errors.New(\"cannot use autogroup:danger-all as a dst\")\n\tErrAutogroupNotSupportedSSHSrc = errors.New(\"autogroup not supported for SSH sources\")\n\tErrAutogroupNotSupportedSSHDst = errors.New(\"autogroup not supported for SSH destinations\")\n\tErrHostNotDefined              = errors.New(\"host not defined in policy\")\n\tErrSSHSourceAliasNotSupported  = errors.New(\"alias not supported for SSH source\")\n\tErrSSHDestAliasNotSupported    = errors.New(\"alias not supported for SSH destination\")\n\tErrUnknownField                = errors.New(\"unknown field\")\n\tErrProtocolNoSpecificPorts     = errors.New(\"protocol does not support specific ports\")\n\tErrTestEmptyAssertions         = errors.New(\"test entry must have at least one of \\\"accept\\\" or \\\"deny\\\"\")\n\tErrTestProtocolNotAllowed      = errors.New(\"test protocol must be tcp, udp, sctp, or empty\")\n\tErrTestDestinationMultiPort    = errors.New(\"test destination port must be a single port\")\n\tErrTestDestinationCIDR         = errors.New(\"test destination must be a single host, not a CIDR range\")\n\tErrAutogroupInternetTestDst    = errors.New(\"autogroup:internet not valid as a test destination\")\n\tErrSSHTestEmptySrc             = errors.New(\"SSH tests entry must have a non-empty src\")\n\tErrSSHTestEmptyDst             = errors.New(\"SSH tests entry must have at least one dst\")\n\tErrSSHTestDstUnknownTag        = errors.New(\"SSH tests dst contains unknown tag\")\n\tErrSSHTestDstDisallowedElement = errors.New(\"SSH tests dst contains disallowed element\")\n)\n\ntype resolved struct {\n\tips netipx.IPSet\n}\n\nfunc newResolved(ipb *netipx.IPSetBuilder) (resolved, error) {\n\tips, err := ipb.IPSet()\n\tif err != nil {\n\t\treturn resolved{}, err\n\t}\n\n\treturn resolved{ips: *ips}, nil\n}\n","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L134-L170","documentation":"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).","triggerScenarios":"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.","commonSituations":"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).","solutions":["Add a src to the flagged sshTests entry — a user (user:*, user:name), group, tag, or autogroup valid for SSH sources","If the entry was scaffolding, delete it","Re-validate; remaining sshTests shape errors (empty dst, bad dst) are reported together"],"exampleFix":"// before\n\"sshTests\": [{\"dst\": [\"tag:server\"], \"accept\": [\"root\"]}]\n// after\n\"sshTests\": [{\"src\": \"group:admin\", \"dst\": [\"tag:server\"], \"accept\": [\"root\"]}]","handlingStrategy":"validation","validationCode":"// Before marshalling a policy, ensure every sshTests entry has src set\nfunc sshTestEntriesValid(tests []SSHPolicyTest) bool {\n    for _, t := range tests {\n        if t.Src == nil { return false }\n    }\n    return true\n}","typeGuard":null,"tryCatchPattern":"if errors.Is(err, policyv2.ErrSSHTestEmptySrc) {\n    // add a user/group/tag/autogroup src to the flagged entry (index in message)\n}","preventionTips":["sshTests entries always need both src and dst","Do not commit scaffolding entries","Remember whitespace-only src collapses to nil and hits this same error"],"tags":["policy","ssh","tests","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}