vxcontrol/pentagi · error
token is malformed
Error message
token is malformed
What it means
ValidateAPIToken parses an API-token JWT (HS256, keyed by MakeJWTSigningKey(globalSalt)). When jwt/v5 parsing fails with jwt.ErrTokenMalformed the function re-wraps it as "token is malformed", meaning the string itself is not a decodable JWT (bad compact JWS structure, invalid base64, unparsable JSON header/payload).
Source
Thrown at backend/pkg/server/auth/api_token_jwt.go:49
ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(ttl) * time.Second)),
IssuedAt: jwt.NewNumericDate(now),
Subject: "api_token",
},
}
}
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
- Fix the client to send the exact token string returned by the token-creation API (MakeAPIToken) in the Authorization: Bearer header
- Log the received Authorization value (length/segment count) and compare against the stored token to spot truncation or corruption
- Check for middleware/proxies that rewrite or trim the Authorization header
- Ensure the client does not double-encode (e.g. base64 the token again before sending)
Example fix
// before
req.Header.Set("Authorization", "Bearer " + base64.StdEncoding.EncodeToString([]byte(token)))
// after
req.Header.Set("Authorization", "Bearer " + token) Defensive patterns
Strategy: validation
Validate before calling
// before sending
parts := strings.Split(token, ".")
if len(parts) != 3 || token == "" {
return fmt.Errorf("token must be a 3-segment JWT, got %d segments", len(parts))
} Type guard
func isJWTShape(s string) bool {
parts := strings.Split(s, ".")
return len(parts) == 3 && parts[0] != "" && parts[1] != ""
} Prevention
- Send the token exactly as returned by the API; never re-encode it
- Assert the Authorization header value equals "Bearer " + token before dispatching requests
- Log token length/segment count (not the token) on auth failures to catch truncation
- Check proxies/clients for header rewriting
When it happens
Trigger: Calling ValidateAPIToken (via bearer-token auth in tryProtoTokenAuthentication) with a string that is empty, truncated, has wrong number of dot-separated segments, contains non-base64 characters, or whose payload/header is not valid JSON.
Common situations: Client sends a session cookie or other non-JWT string in the Authorization header; token truncated by an HTTP proxy or copied with missing characters; client encodes token with different padding/URL-safe base64 variant; tests passing placeholder strings like "test-token".
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- token is invalid
- token is either expired or not active yet
- token invalid: %w
- token is invalid
- cookie claim invalid
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/764f1565fb15fb39.
Report an issue: GitHub.