vxcontrol/pentagi · error
token invalid: %w
Error message
token invalid: %w
What it means
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.
Source
Thrown at backend/pkg/server/auth/api_token_jwt.go:53
}
}
func ValidateAPIToken(tokenString, globalSalt string) (*models.APITokenClaims, error) {
var claims models.APITokenClaims
token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (any, error) {
// verify signing algorithm to prevent "alg: none"
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return MakeJWTSigningKey(globalSalt), nil
})
if err != nil {
if errors.Is(err, jwt.ErrTokenMalformed) {
return nil, fmt.Errorf("token is malformed")
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
return nil, fmt.Errorf("token is either expired or not active yet")
} else {
return nil, fmt.Errorf("token invalid: %w", err)
}
}
if !token.Valid {
return nil, fmt.Errorf("token is invalid")
}
return &claims, nil
}
View on GitHub (pinned to ea665308ba)
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
Example fix
// before // .env on replica B differs GLOBAL_SALT=different-salt // after // match the issuing server's salt on every replica GLOBAL_SALT=original-salt
Defensive patterns
Strategy: try-catch
Type guard
func isSignatureError(err error) bool {
return err != nil &&
!strings.Contains(err.Error(), "malformed") &&
!strings.Contains(err.Error(), "expired or not active yet") &&
strings.Contains(err.Error(), "token invalid")
} Try / catch
if err != nil {
if strings.HasPrefix(err.Error(), "token invalid:") {
// signature/key mismatch: re-issue token for this installation
token = createTokenWithCurrentSalt()
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- token is invalid
- token is malformed
- token is either expired or not active yet
- token is invalid
- cookie claim invalid
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/265a83fa5c10b734.
Report an issue: GitHub.