{"record":{"id":"6243ccb18905b154","repo":"juanfont/headscale","slug":"invalid-ssh-action","errorCode":null,"errorMessage":"invalid SSH action","messagePattern":"invalid SSH action","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"info","filePath":"hscontrol/policy/v2/types.go","lineNumber":130,"sourceCode":"\tErrInvalidTagFormat            = errors.New(\"tag must start with 'tag:'\")\n\tErrInvalidHostname             = errors.New(\"invalid hostname\")\n\tErrHostResolve                 = errors.New(\"error resolving host\")\n\tErrInvalidPrefix               = errors.New(\"invalid prefix\")\n\tErrInvalidAutogroup            = errors.New(\"invalid autogroup\")\n\tErrUnknownAutogroup            = errors.New(\"unknown autogroup\")\n\tErrHostportMissingColon        = errors.New(\"hostport must contain a colon\")\n\tErrTypeNotSupported            = errors.New(\"type not supported\")\n\tErrInvalidAlias                = errors.New(\"invalid alias format\")\n\tErrInvalidAutoApprover         = errors.New(\"invalid auto approver format\")\n\tErrInvalidOwner                = errors.New(\"invalid owner format\")\n\tErrGroupNotDefined             = errors.New(\"group not defined in policy\")\n\tErrInvalidGroupMember          = errors.New(\"invalid group member type\")\n\tErrGroupValueNotArray          = errors.New(\"group value must be an array of users\")\n\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\")","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L112-L148","documentation":"Declared at hscontrol/policy/v2/types.go:130 but currently has NO production return site: SSHAction.UnmarshalJSON actually reports invalid values with the separate sentinel ErrSSHActionInvalid (\"is not a valid action\", types.go:55) at types.go:1642. Valid SSH actions are \"accept\" and \"check\" (plus empty string, which per-rule Validate() rejects later). If you are matching on this sentinel with errors.Is you will never match; match ErrSSHActionInvalid instead.","triggerScenarios":"An SSH rule with \"action\": \"acceptx\" or \"deny\" produces `\"deny\" is not a valid action` wrapping ErrSSHActionInvalid — not this error. This sentinel would only appear if code explicitly returned it, which none currently does.","commonSituations":"Developers writing error-matching code against the sentinel list in types.go and wondering why errors.Is never fires; users seeing an invalid-SSH-action message and grepping for the wrong sentinel.","solutions":["Use \"accept\" or \"check\" as SSH action values","In Go, match the runtime error with errors.Is(err, policy.ErrSSHActionInvalid), not ErrInvalidSSHAction","If you maintain this package, consider removing or wiring up the unused sentinel to avoid confusion"],"exampleFix":"// before (Go error matching that never fires)\nif errors.Is(err, policy.ErrInvalidSSHAction) { ... }\n\n// after\nif errors.Is(err, policy.ErrSSHActionInvalid) { ... }","handlingStrategy":"try-catch","validationCode":"const validSSHActions = map[string]bool{\"accept\": true, \"check\": true, \"\": true}\nfor _, r := range rawSSH {\n\tif !validSSHActions[r.Action] {\n\t\treturn fmt.Errorf(\"ssh action must be accept or check, got %q\", r.Action)\n\t}\n}","typeGuard":"// NOTE: runtime uses ErrSSHActionInvalid, not ErrInvalidSSHAction\nfunc isInvalidSSHAction(err error) bool {\n\treturn errors.Is(err, policy.ErrSSHActionInvalid) || errors.Is(err, policy.ErrInvalidSSHAction)\n}","tryCatchPattern":"if err := json.Unmarshal(b, &p); err != nil {\n\t// the actually-returned sentinel is ErrSSHActionInvalid (\"is not a valid action\")\n\tif errors.Is(err, policy.ErrSSHActionInvalid) {\n\t\treturn fmt.Errorf(\"ssh action must be accept or check: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Restrict SSH actions to the literal strings accept and check in templates","When writing errors.Is checks, verify the sentinel against its return sites, not just the var block","Track headscale's policy v2 cleanup — unused sentinels may be wired up or removed"],"tags":["policy","ssh","dead-code","sentinel","go"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}