Overview
The CVE-2026-35042 advisory describes a vulnerability in the fast-jwt library, where versions 6.1.0 and earlier fail to validate the crit (Critical) Header Parameter per RFC 7515 §4.1.11. If a JWS token includes a crit array listing extensions that fast-jwt does not understand, the library may accept the token instead of rejecting it. In practice, this means an attacker could craft a token with unknown critical headers to bypass certain validation logic, potentially bypassing authorization checks and enabling access to protected resources. This class of weakness maps to CWE-345 (Weakened encryption or data security) and CWE-636 (Here-document injection or similar header misuse), and it directly jeopardizes object-level authorization when an API relies on JWT claims for per-object access decisions. When an API implemented with Go and Gin trusts the token contents for object ownership or permissions without ensuring the server rejects unknown critical headers, attackers can access or manipulate resources they should not be allowed to. The real-world risk is especially acute in endpoints performing Broken Object Property Level Authorization, where a user might fetch, modify, or delete resources by manipulating the token rather than enforcing server-side authorization checks.
In practice, attackers could present a valid JWT with a crit header containing unknown entries. If the library accepts such tokens, the server may proceed with its normal claim-based checks, but those checks may not reflect the intended validation semantics for those unknown header extensions. This undermines the principle of explicit, server-enforced authorization at the object level, allowing unauthorized access to specific objects identified by path or query parameters. The mitigation reduces to upgrading to a version of fast-jwt that validates crit headers and/or adding explicit server-side checks that reject tokens containing unrecognized crit entries, combined with robust per-object authorization logic in Gin handlers.
This guide explains the impact in a Go (Gin) application, demonstrates the vulnerable pattern and a concrete fix, and emphasizes per-object authorization as a defense in depth measure. It also includes test-oriented guidance to verify that tokens with unknown crit headers are rejected and that legitimate, properly scoped tokens grant access only to permitted objects.
Affected Versions
fast-jwt <= 6.1.0
Code Fix Example
Go (Gin) API Security Remediation
Vulnerable pattern (illustrative, using gin and a JWT library):
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
)
var signingKey = []byte("secret")
func main() {
r := gin.Default()
r.GET("/resources/:id", AuthMiddlewareVulnerable(), resourceHandler)
_ = r.Run(":8080")
}
// Vulnerable middleware: validates signature but ignores crit header issues
func AuthMiddlewareVulnerable() gin.HandlerFunc {
return func(c *gin.Context) {
auth := c.GetHeader("Authorization")
if !strings.HasPrefix(auth, "Bearer ") {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(auth, "Bearer ")
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// HS256 signing key
return signingKey, nil
})
if err != nil || !token.Valid {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
// Extract user identity from claims (subject)
if claims, ok := token.Claims.(jwt.MapClaims); ok {
if sub, ok := claims["sub"].(string); ok {
c.Set("userID", sub)
}
}
c.Next()
}
}
func resourceHandler(c *gin.Context) {
userID := c.GetString("userID")
resourceID := c.Param("id")
ownerID := getOwnerFromDB(resourceID) // pseudo DB call
if ownerID != userID {
c.AbortWithStatus(http.StatusForbidden)
return
}
c.JSON(http.StatusOK, gin.H{"resource": resourceID, "owner": ownerID})
}
// Placeholder for DB access
func getOwnerFromDB(id string) string {
// In real code, fetch owner from DB
return "owner-123"
}
// ---------------------------------------------------------------------------------
// Fixed pattern: upgrade fast-jwt to a version that validates crit headers and/or add
// a server-side check to reject tokens with unknown crit entries, along with
// explicit per-object authorization checks.
// Note: The code below demonstrates how to perform an explicit crit header check when
// using a generic JWT library. In production, prefer upgrading fast-jwt to a version
// that enforces this by default.
// ---------------------------------------------------------------------------------
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
)
var signingKey = []byte("secret")
type WinterWhitelist struct {
AllowedCrits map[string]bool
}
func mainFixed() {
r := gin.Default()
r.GET("/resources/:id", AuthMiddlewareFixed(), resourceHandlerFixed)
_ = r.Run(":8080")
}
var whitelist = WinterWhitelist{AllowedCrits: map[string]bool{"ext1": true, "ext2": true}}
func AuthMiddlewareFixed() gin.HandlerFunc {
return func(c *gin.Context) {
auth := c.GetHeader("Authorization")
if !strings.HasPrefix(auth, "Bearer ") {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(auth, "Bearer ")
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return signingKey, nil
})
if err != nil || !token.Valid {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
// Explicit crit header validation: reject tokens with unknown/unsupported crit entries
if critRaw, ok := token.Header["crit"]; ok {
if critList, ok := critRaw.([]interface{}); ok {
for _, v := range critList {
if s, ok := v.(string); ok {
if !whitelist.AllowedCrits[s] {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
}
}
}
}
// Optional: still extract user identity from claims
if claims, ok := token.Claims.(jwt.MapClaims); ok {
if sub, ok := claims["sub"].(string); ok {
c.Set("userID", sub)
}
}
c.Next()
}
}
func resourceHandlerFixed(c *gin.Context) {
userID := c.GetString("userID")
resourceID := c.Param("id")
ownerID := getOwnerFromDB(resourceID)
if ownerID != userID {
c.AbortWithStatus(http.StatusForbidden)
return
}
c.JSON(http.StatusOK, gin.H{"resource": resourceID, "owner": ownerID})
}
// Placeholder for DB access
func getOwnerFromDB(id string) string {
return "owner-123"
}