{"record":{"id":"a0e703c5f7be563f","repo":"hashicorp/nomad","slug":"identities-without-an-expiration-are-insecure","errorCode":null,"errorMessage":"identities without an expiration are insecure","messagePattern":"identities without an expiration are insecure","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"nomad/structs/workload_id.go","lineNumber":515,"sourceCode":"\treturn mErr.ErrorOrNil()\n}\n\nfunc (wi *WorkloadIdentity) Warnings() error {\n\tif wi == nil {\n\t\treturn fmt.Errorf(\"must not be nil\")\n\t}\n\n\tvar mErr multierror.Error\n\n\tif n := len(wi.Audience); n == 0 {\n\t\tmErr.Errors = append(mErr.Errors, fmt.Errorf(\"identities without an audience are insecure\"))\n\t} else if n > 1 {\n\t\tmErr.Errors = append(mErr.Errors, fmt.Errorf(\"while multiple audiences is allowed, it is more secure to use 1 audience per identity\"))\n\t}\n\n\tif wi.Name != \"\" && wi.Name != WorkloadIdentityDefaultName {\n\t\tif wi.TTL == 0 {\n\t\t\tmErr.Errors = append(mErr.Errors, fmt.Errorf(\"identities without an expiration are insecure\"))\n\t\t}\n\t}\n\n\t// Warn users about using env vars without restarts\n\tif wi.Env && wi.ChangeMode != WIChangeModeRestart {\n\t\tmErr.Errors = append(mErr.Errors, fmt.Errorf(\"using env=%t without change_mode=%q may result in task not getting updated identity\",\n\t\t\twi.Env, WIChangeModeRestart))\n\t}\n\n\treturn mErr.ErrorOrNil()\n}\n\n// WorkloadIdentityRequest encapsulates the 3 parameters used to generated a\n// signed workload identity: the alloc, task, and specific identity's name.\ntype WorkloadIdentityRequest struct {\n\tAllocID string\n\tWIHandle\n}","sourceCodeStart":497,"sourceCodeEnd":533,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/workload_id.go#L497-L533","documentation":"WorkloadIdentity.Warnings flags named identities that have no TTL, meaning the resulting JWT never expires. Non-expiring identity tokens are insecure; Nomad recommends setting a TTL so tokens are periodically rotated.","triggerScenarios":"Calling Warnings() on an identity where wi.Name is non-empty and not \"default\" while wi.TTL == 0, e.g. identity { name = \"aws\" audience = [\"aws\"] } with no ttl set.","commonSituations":"Adding named identities for cloud credentials without configuring expiration; migrating default identities to named ones and dropping the TTL; assuming tokens are short-lived by default.","solutions":["Set a ttl on the named identity block (e.g. ttl = \"1h\").","Set wi.TTL in Go to a positive duration before validation.","Choose a TTL aligned with the cloud provider's credential lifetime and rotation policy."],"exampleFix":"// before\nidentity {\n  name = \"aws\"\n  audience = [\"aws\"]\n}\n// after\nidentity {\n  name = \"aws\"\n  audience = [\"aws\"]\n  ttl = \"1h\"\n}","handlingStrategy":"validation","validationCode":"func validateTTLSet(wi *structs.WorkloadIdentity) error {\n  if wi.Name != \"\" && wi.Name != structs.WorkloadIdentityDefaultName && wi.TTL == 0 {\n    return fmt.Errorf(\"identity %q should set a ttl so tokens expire\", wi.Name)\n  }\n  return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Include a default ttl line in every named identity template.","Review named identities added without expiration during code review.","Align TTL values with cloud provider credential lifetime policies."],"tags":["nomad","workload-identity","security","jwt"],"backgroundTag":"jwt-token-expired","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"}