{"record":{"id":"51d426c2058cf419","repo":"juanfont/headscale","slug":"invalid-action","errorCode":null,"errorMessage":"invalid action","messagePattern":"invalid action","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/filter.go","lineNumber":20,"sourceCode":"\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/juanfont/headscale/hscontrol/types\"\n\t\"github.com/juanfont/headscale/hscontrol/util\"\n\t\"github.com/rs/zerolog/log\"\n\t\"go4.org/netipx\"\n\t\"tailscale.com/tailcfg\"\n\t\"tailscale.com/types/views\"\n)\n\nvar (\n\tErrInvalidAction = errors.New(\"invalid action\")\n\terrSelfInSources = errors.New(\"autogroup:self cannot be used in sources\")\n)\n\n// companionCap pairs a well-known Tailscale capability with its\n// companion capability.\ntype companionCap struct {\n\toriginal  tailcfg.PeerCapability\n\tcompanion tailcfg.PeerCapability\n}\n\n// companionCaps lists certain well-known Tailscale capabilities and\n// their companion capability. When a grant includes one of these\n// capabilities, Tailscale automatically generates an additional\n// [tailcfg.FilterRule] with the companion capability and a nil CapMap value.\n// The slice is ordered by the original capability name so that\n// generated companion rules are emitted deterministically.\nvar companionCaps = []companionCap{\n\t{tailcfg.PeerCapabilityTaildrive, tailcfg.PeerCapabilityTaildriveSharer},","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/filter.go#L2-L38","documentation":"Exported sentinel declared in hscontrol/policy/v2/filter.go:20 and raised during policy parsing when a grant's action field is not one of the valid values. In the ACL/grants model, 'action' only admits SSH-rule actions ('accept' or 'check'); anything else fails unmarshaling with this error before any filter compilation happens.","triggerScenarios":"A policy containing a grants or acls entry with e.g. \"action\": \"deny\" or a typo like \"accpet\". The HuJSON/JSON unmarshal path for grant actions rejects the value (see types_test.go 'invalid action should fail' and TestACL_UnmarshalJSON_InvalidAction).","commonSituations":"Migrating from a firewall mindset and writing 'deny' actions (Tailscale ACLs are default-deny, only accept/check exist); typo in an IaC/terraform-managed policy; hand-editing HuJSON.","solutions":["Change the action to \"accept\" — deny semantics come from omitting the grant, since the policy is default-deny","For SSH rules that re-check periodically, use \"check\" with a checkPeriod","Validate the policy with 'headscale policy check' before applying"],"exampleFix":"// before\n{\"src\": [\"group:admin\"], \"dst\": [\"tag:server:22\"], \"action\": \"deny\"}\n\n// after (omit the grant entirely for deny; use accept/check otherwise)\n{\"src\": [\"group:admin\"], \"dst\": [\"tag:server:22\"], \"action\": \"accept\"}","handlingStrategy":"validation","validationCode":"func validSSHAction(a string) bool { return a == \"accept\" || a == \"check\" }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember policies are default-deny: express 'deny' by omission","Lint policies with 'headscale policy check' in CI","Generate policies with templating/IaC that only emits accept/check"],"tags":["policy","acl","config","headscale"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}