{"record":{"id":"265a83fa5c10b734","repo":"vxcontrol/pentagi","slug":"token-invalid-w","errorCode":null,"errorMessage":"token invalid: %w","messagePattern":"token invalid: %w","errorType":"validation","errorClass":null,"httpStatus":401,"severity":"error","filePath":"backend/pkg/server/auth/api_token_jwt.go","lineNumber":53,"sourceCode":"\t}\n}\n\nfunc ValidateAPIToken(tokenString, globalSalt string) (*models.APITokenClaims, error) {\n\tvar claims models.APITokenClaims\n\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":35,"sourceCodeEnd":63,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/auth/api_token_jwt.go#L35-L63","documentation":"ValidateAPIToken falls through to \"token invalid: %w\" for any parse/verify error that is neither malformed nor expired/not-yet-valid. Typically this is a signature verification failure (wrong signing key) or a claims-validation error such as invalid subject/issuer or unusable claims.","triggerScenarios":"Validating a token signed with a different globalSalt than the one configured on this server (MakeJWTSigningKey mismatch); token signed with RS/ES key rejected by the HMAC check surfaced as unexpected signing method wrapped here; corrupted token bytes that still decode.","commonSituations":"Server's GLOBAL_SALT env changed or differs between replicas/load balancers; token issued by a different PentAGI installation; database restored/shared across environments with different salts; a v3/v4 token format from an older version.","solutions":["Ensure GLOBAL_SALT (globalSalt) is identical across all server instances and unchanged since the token was issued; issue a new token after any salt change","Inspect the wrapped cause (%w) to distinguish signature failure from claims failure","Re-create the API token in the database/settings UI so it is signed with the current salt","Verify only one installation/namespace is using the same database or token store"],"exampleFix":"// before\n// .env on replica B differs\nGLOBAL_SALT=different-salt\n// after\n// match the issuing server's salt on every replica\nGLOBAL_SALT=original-salt","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isSignatureError(err error) bool {\n    return err != nil &&\n        !strings.Contains(err.Error(), \"malformed\") &&\n        !strings.Contains(err.Error(), \"expired or not active yet\") &&\n        strings.Contains(err.Error(), \"token invalid\")\n}","tryCatchPattern":"if err != nil {\n    if strings.HasPrefix(err.Error(), \"token invalid:\") {\n        // signature/key mismatch: re-issue token for this installation\n        token = createTokenWithCurrentSalt()\n    }\n    return err\n}","preventionTips":["Keep GLOBAL_SALT identical and version-controlled (secret manager) across all replicas","Never share one database between installations with different salts","After rotating the salt, invalidate and re-issue every token","Unwrap the cause with errors.Unwrap to distinguish signature vs claims failures"],"tags":["jwt","signature-verification","authentication"],"backgroundTag":"jwt-signature-verification-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}