{"record":{"id":"340e61cef88a5525","repo":"hashicorp/nomad","slug":"token-expiration-ttl-s-should-not-be-negative","errorCode":null,"errorMessage":"token expiration TTL '%s' should not be negative","messagePattern":"token expiration TTL '(.+?)' should not be negative","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/acl.go","lineNumber":787,"sourceCode":"\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\n\t\tif a.ExpirationTime != nil && !a.ExpirationTime.IsZero() {\n\n\t\t\tif a.CreateTime.After(*a.ExpirationTime) {\n\t\t\t\tmErr.Errors = append(mErr.Errors, errors.New(\"expiration time cannot be before create time\"))\n\t\t\t}\n\n\t\t\t// Create a time duration which details the time-til-expiry, so we can\n\t\t\t// check this against the regions max and min values.\n\t\t\texpiresIn := a.ExpirationTime.Sub(a.CreateTime)\n\t\t\tif expiresIn > maxTTL {\n\t\t\t\tmErr.Errors = append(mErr.Errors,\n\t\t\t\t\tfmt.Errorf(\"expiration time cannot be more than %s in the future (was %s)\",\n\t\t\t\t\t\tmaxTTL, expiresIn))\n\n\t\t\t} else if expiresIn < minTTL {\n\t\t\t\tmErr.Errors = append(mErr.Errors,","sourceCodeStart":769,"sourceCodeEnd":805,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/acl.go#L769-L805","documentation":"ACLToken validation (create path, existing == nil) rejects a token whose ExpirationTTL is negative with 'token expiration TTL %q should not be negative'. TTLs are parsed to time.Duration; a negative value can never represent a valid time-til-expiry, so Nomad fails the request before computing ExpirationTime.","triggerScenarios":"Creating an ACL token (`nomad acl token create -ttl ...` or the ACL token API) with a negative ExpirationTTL, e.g. -5m, often from arithmetic or a misconfigured variable.","commonSituations":"Scripts computing TTL as (start - end) accidentally inverted; config files with placeholder values like '-1' meaning 'unset' that get parsed as a duration; templating bugs injecting a minus sign.","solutions":["Provide a positive TTL (e.g. 1h) or omit ExpirationTTL entirely for a non-expiring token.","Audit scripts for inverted duration arithmetic and clamp TTLs to >= 0 before the API call.","If 'no expiration' was intended, ensure the field is zero/nil rather than a sentinel negative value."],"exampleFix":"// before\ntoken.ExpirationTTL = -1 * time.Hour // sentinel for 'no expiry'\n// after\nvar token.ExpirationTTL time.Duration // leave zero for non-expiring token","handlingStrategy":"validation","validationCode":"func validateExpirationTTL(ttl time.Duration) error {\n    if ttl < 0 {\n        return fmt.Errorf(\"expiration TTL %s is negative\", ttl)\n    }\n    return nil\n}","typeGuard":"func ttlUsable(d *time.Duration) bool {\n    return d == nil || *d >= 0\n}","tryCatchPattern":"if err := token.Validate(nil); err != nil {\n    if strings.Contains(err.Error(), \"should not be negative\") {\n        return fmt.Errorf(\"fix TTL (no sentinel negatives): %w\", err)\n    }\n    return err\n}","preventionTips":["Never use negative durations as 'unset' sentinels; use zero value or omit the field.","Guard duration arithmetic: compute TTL as end.Sub(start) and assert >= 0.","Type-check config loaders so bare '-1' strings do not parse into TTL fields.","Add pre-submit validation of all token TTL inputs."],"tags":["acl","token","validation","ttl"],"backgroundTag":"acl-token-ttl-invalid","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"}