{"record":{"id":"78ea928c5e3499ce","repo":"juanfont/headscale","slug":"test-destination-must-be-a-single-host-not-a-cidr","errorCode":null,"errorMessage":"test destination must be a single host, not a CIDR range","messagePattern":"test destination must be a single host, not a CIDR range","errorType":"validation","errorClass":"ErrTestDestinationCIDR","httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":150,"sourceCode":"\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) {\n\tips, err := ipb.IPSet()\n\tif err != nil {\n\t\treturn resolved{}, err\n\t}\n\n\treturn resolved{ips: *ips}, nil","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L132-L168","documentation":"ErrTestDestinationCIDR is returned by validateTestDestination (hscontrol/policy/v2/types.go:3258-3267) when a tests destination names a CIDR range rather than one specific host. Both raw /N syntax on the dst string and a hosts:-table alias whose RHS prefix is wider than a single address (Bits < Addr().BitLen) are rejected; bare IP literals (implicit /32 or /128) are fine.","triggerScenarios":"Test dst like \"10.0.0.0/8:80\" (raw slash in input), or a hosts entry \"servers = 10.1.0.0/16\" used as \"servers:80\" in a test — prefix.Bits() < Addr().BitLen() triggers the error.","commonSituations":"Using a subnet alias defined in hosts for convenience in tests; migrating integration assertions into tests blocks and keeping subnet destinations; assuming tests behave like ACL dsts where CIDRs are valid.","solutions":["Point the test dst at one concrete host IP, e.g. \"10.1.2.3:80\"","Or define/point at a hosts alias whose value is a single address (full-length prefix)","Keep subnet-wide assertions in grants; test a representative single host instead"],"exampleFix":"// before\n\"hosts\": {\"servers\": \"10.1.0.0/16\"}\n\"tests\": [{\"src\": \"user1\", \"accept\": [\"servers:80\"]}]\n// after\n\"hosts\": {\"web1\": \"10.1.0.5\"}\n\"tests\": [{\"src\": \"user1\", \"accept\": [\"web1:80\"]}]","handlingStrategy":"validation","validationCode":"// Verify a hosts alias is a single address before using it in tests\nfunc hostIsSingleAddress(prefix netip.Prefix) bool {\n    return prefix.Bits() == prefix.Addr().BitLen()\n}","typeGuard":null,"tryCatchPattern":"if errors.Is(err, policyv2.ErrTestDestinationCIDR) {\n    // replace the CIDR/host alias dst with one concrete host IP\n}","preventionTips":["Use bare IP literals or single-address hosts aliases in tests","Never put /N syntax in a tests destination","Keep subnet destinations in grants, not tests"],"tags":["policy","tests","cidr","hosts","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}