{"record":{"id":"8ad79cfc7bd8171c","repo":"hashicorp/nomad","slug":"client-token-missing-policies-or-roles","errorCode":null,"errorMessage":"client token missing policies or roles","messagePattern":"client token missing policies or roles","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/acl.go","lineNumber":771,"sourceCode":"\t}\n}\n\n// Validate is used to check a token for reasonableness\nfunc (a *ACLToken) Validate(minTTL, maxTTL time.Duration, existing *ACLToken) error {\n\tvar mErr multierror.Error\n\n\t// The human friendly name of an ACL token cannot exceed 256 characters.\n\tif len(a.Name) > maxTokenNameLength {\n\t\tmErr.Errors = append(mErr.Errors, errors.New(\"token name too long\"))\n\t}\n\n\t// The type of an ACL token must be set. An ACL token of type client must\n\t// have associated policies or roles, whereas a management token cannot be\n\t// associated with policies.\n\tswitch a.Type {\n\tcase ACLClientToken:\n\t\tif len(a.Policies) == 0 && len(a.Roles) == 0 {\n\t\t\tmErr.Errors = append(mErr.Errors, errors.New(\"client token missing policies or roles\"))\n\t\t}\n\tcase ACLManagementToken:\n\t\tif len(a.Policies) != 0 || len(a.Roles) != 0 {\n\t\t\tmErr.Errors = append(mErr.Errors, errors.New(\"management token cannot be associated with policies or roles\"))\n\t\t}\n\tdefault:\n\t\tmErr.Errors = append(mErr.Errors, errors.New(\"token type must be client or management\"))\n\t}\n\n\t// There are different validation rules depending on whether the ACL token\n\t// is being created or updated.\n\tswitch existing {\n\tcase nil:\n\t\tif a.ExpirationTTL < 0 {\n\t\t\tmErr.Errors = append(mErr.Errors,\n\t\t\t\tfmt.Errorf(\"token expiration TTL '%s' should not be negative\", a.ExpirationTTL))\n\t\t}\n","sourceCodeStart":753,"sourceCodeEnd":789,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/acl.go#L753-L789","documentation":"A validation error from ACLToken.Validate: a client-type ACL token must be associated with at least one policy or one role. A token of type client with empty Policies and empty Roles is rejected at create/update time because it would grant nothing useful (or ambiguously nothing).","triggerScenarios":"Upserting an ACLToken with Type == \"client\" while len(Policies) == 0 and len(Roles) == 0.","commonSituations":"Automation creating tokens from templates that forget to attach policies; token creation UI/API where role assignment was skipped; refactors that moved permissions from policies to roles but left both lists empty.","solutions":["Attach at least one policy name to the token's Policies list","Or assign at least one role via the Roles list","Use Type \"management\" only if unrestricted access is actually intended","Validate client tokens carry policies or roles before calling the API"],"exampleFix":"// before\ntoken := &api.ACLToken{Name: \"ci\", Type: \"client\"}\n// after\ntoken := &api.ACLToken{Name: \"ci\", Type: \"client\", Policies: []string{\"read-only\"}}","handlingStrategy":"validation","validationCode":"if token.Type == \"client\" && len(token.Policies) == 0 && len(token.Roles) == 0 {\n    return errors.New(\"client tokens require at least one policy or role\")\n}","typeGuard":"func clientTokenHasGrants(t *structs.ACLToken) bool {\n    return t.Type != structs.ACLClientToken || len(t.Policies) > 0 || len(t.Roles) > 0\n}","tryCatchPattern":"if err := token.Validate(minTTL, maxTTL, nil); err != nil {\n    if strings.Contains(err.Error(), \"client token missing policies or roles\") {\n        token.Policies = append(token.Policies, \"default\")\n        err = token.Validate(minTTL, maxTTL, nil)\n    }\n}","preventionTips":["Attach at least one policy or role when creating client tokens","Run ACLToken.Validate locally before the RPC call","Audit token-creation templates to ensure grants are populated"],"tags":["nomad","acl","token","validation"],"backgroundTag":"input-validation-failed","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}