{"record":{"id":"cbb31a7752d47955","repo":"juanfont/headscale","slug":"alias-not-supported-for-ssh-destination","errorCode":null,"errorMessage":"alias not supported for SSH destination","messagePattern":"alias not supported for SSH destination","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":144,"sourceCode":"\tErrInvalidHostIP               = errors.New(\"hostname contains invalid IP address\")\n\tErrTagNotDefined               = errors.New(\"tag not found\")\n\tErrAutoApproverNotAlias        = errors.New(\"auto approver is not an alias\")\n\tErrInvalidACLAction            = errors.New(\"invalid ACL action\")\n\tErrInvalidSSHAction            = errors.New(\"invalid SSH action\")\n\tErrInvalidProtocolNumber       = errors.New(\"invalid protocol number\")\n\tErrProtocolLeadingZero         = errors.New(\"leading 0 not permitted in protocol number\")\n\tErrProtocolOutOfRange          = errors.New(\"protocol number out of range (0-255)\")\n\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) {","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L126-L162","documentation":"Returned by SSHDstAliases.UnmarshalJSON (hscontrol/policy/v2/types.go:2941) when an element of an SSH rule's dst array decodes to a type other than *Username, *Tag, *AutoGroup, or *Host. Groups and raw IP/CIDR literals are rejected: SSH destinations are devices, identified by tag, host alias, autogroup, or (for self-SSH) a user. A \"*\" dst gets its own richer error (ErrSSHWildcardDestination, 2937).","triggerScenarios":"An SSH rule with dst containing \"group:admin\" (groups are user sets, not devices), or a raw address like \"100.64.0.5\" / \"10.0.0.0/8\". The rejected Go type is included in the message.","commonSituations":"Assuming group: works in SSH dst because it works in SSH src; pasting ACL dst lines with CIDRs into ssh rules; using \"*\" and expecting it to work (it produces the wildcard-specific error suggesting autogroup:member/autogroup:tagged).","solutions":["Use a tag (\"tag:server\"), host alias, autogroup:member/tagged/self, or a username as SSH dst","Replace \"*\" with autogroup:member (user-owned devices) or autogroup:tagged (tagged devices)","Move group-based targeting to src, where groups are legal"],"exampleFix":"// before\n\"ssh\": [{\"action\": \"accept\", \"src\": [\"group:admin\"], \"dst\": [\"group:servers\"], \"users\": [\"root\"]}]\n\n// after\n\"ssh\": [{\"action\": \"accept\", \"src\": [\"group:admin\"], \"dst\": [\"tag:server\"], \"users\": [\"root\"]}]","handlingStrategy":"validation","validationCode":"func validSSHDst(s string) bool {\n\tif s == \"*\" {\n\t\treturn false // gets wildcard-specific error\n\t}\n\treturn strings.HasPrefix(s, \"autogroup:\") || strings.HasPrefix(s, \"tag:\") ||\n\t\tstrings.Contains(s, \"@\") || isDefinedHostName(s)\n}","typeGuard":"func isSSHDestAliasNotSupported(err error) bool {\n\treturn errors.Is(err, policy.ErrSSHDestAliasNotSupported)\n}","tryCatchPattern":"if err := json.Unmarshal(b, &p); err != nil {\n\tif errors.Is(err, policy.ErrSSHDestAliasNotSupported) {\n\t\treturn fmt.Errorf(\"SSH dst must be tag/host/user/autogroup (no groups or IPs): %w\", err)\n\t}\n\treturn err\n}","preventionTips":["SSH dst entries are devices: tag, host alias, user, autogroup:member/tagged/self","Do not reuse group: in dst; groups are user sets","Replace \"*\" dst with autogroup:member or autogroup:tagged for clearer intent"],"tags":["policy","ssh","validation","json"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}