{"record":{"id":"d29d3523dd478467","repo":"vxcontrol/pentagi","slug":"token-is-invalid-d29d35","errorCode":null,"errorMessage":"token is invalid","messagePattern":"token is invalid","errorType":"validation","errorClass":null,"httpStatus":401,"severity":"error","filePath":"backend/pkg/server/auth/api_token_jwt.go","lineNumber":58,"sourceCode":"\ttoken, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (any, error) {\n\t\t// verify signing algorithm to prevent \"alg: none\"\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, fmt.Errorf(\"unexpected signing method: %v\", token.Header[\"alg\"])\n\t\t}\n\t\treturn MakeJWTSigningKey(globalSalt), nil\n\t})\n\tif err != nil {\n\t\tif errors.Is(err, jwt.ErrTokenMalformed) {\n\t\t\treturn nil, fmt.Errorf(\"token is malformed\")\n\t\t} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {\n\t\t\treturn nil, fmt.Errorf(\"token is either expired or not active yet\")\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"token invalid: %w\", err)\n\t\t}\n\t}\n\n\tif !token.Valid {\n\t\treturn nil, fmt.Errorf(\"token is invalid\")\n\t}\n\n\treturn &claims, nil\n}\n","sourceCodeStart":40,"sourceCodeEnd":63,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/auth/api_token_jwt.go#L40-L63","documentation":"After parsing succeeds with no error, ValidateAPIToken double-checks token.Valid; if false it returns \"token is invalid\". With jwt/v5 this is a defensive branch — if ParseWithClaims returned no error, token.Valid is normally true — but it guards against any parser state where claims parsed yet validity was not established.","triggerScenarios":"Practically only reached if a future/patched jwt/v5 behavior returns err==nil with token.Valid==false, or custom claims' validation hooks invalidate the token without returning an error that maps to the other branches.","commonSituations":"Upgrading golang-jwt/jwt versions introduces new validity semantics; test mocks returning a non-valid token with nil error.","solutions":["Treat as a hard authentication failure: reject the request and return 401","Log token subject/tokenID (never the raw token) to identify which credential failed","Pin/verify the golang-jwt/jwt/v5 version for consistent Valid semantics","Report upstream if reproducible, including how the token was constructed"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isHardInvalid(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"token is invalid\")\n}","tryCatchPattern":"claims, err := auth.ValidateAPIToken(tok, salt)\nif err != nil {\n    if strings.Contains(err.Error(), \"token is invalid\") {\n        log.Error(\"parser returned non-valid token without error; failing closed\")\n    }\n    return err // always fail closed\n}","preventionTips":["Fail closed: treat this branch as an unconditional auth rejection","Pin the golang-jwt/jwt/v5 version and review its release notes on upgrades","Avoid custom validation hooks that mutate token validity without returning errors","Report reproducible cases upstream with reproduction steps"],"tags":["jwt","authentication","defensive-check"],"backgroundTag":"jwt-token-invalid","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}