Overview
CVE-2026-40252 describes a broken object-level authorization (also CWE-284, CWE-639) in FastGPT prior to 4.14.10.4 where an authenticated user could access and execute applications belonging to other teams by supplying a foreign appId. Although the API validates the team token, it fails to verify that the requested application actually belongs to the authenticated team, enabling cross-tenant data exposure and unauthorized execution of private AI workflows. This is a classic BOLA defect: authorization is performed at a coarse level (token validation) but not at the resource level (app ownership). In Go with Gin, the vulnerability manifests when routes accept an appId path parameter and return resource data or actions without confirming ownership. The fix, as noted in the CVE, is to enforce resource ownership in each access path and couple identity (team) from the token with per-resource checks, ensuring that app.TeamID == user.TeamID before returning data or triggering actions. This guide demonstrates the impact, how an attacker could exploit it, and a concrete Go (Gin) remediation with side-by-side vulnerable and fixed implementations.
Affected Versions
Prior to 4.14.10.4
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type App struct {
ID string
TeamID string
Name string
}
type User struct {
ID string
TeamID string
}
var apps = map[string]App{
"app-1": App{ID: "app-1", TeamID: "team-a", Name: "Alice App"},
"app-2": App{ID: "app-2", TeamID: "team-b", Name: "Bob App"},
}
var userStore = map[string]User{
"token-a": {ID: "u1", TeamID: "team-a"},
"token-b": {ID: "u2", TeamID: "team-b"},
}
func main() {
r := gin.Default()
// Mock authentication middleware: extracts user from a Bearer token and stores in context
r.Use(func(c *gin.Context) {
auth := c.GetHeader("Authorization")
token := ""
if len(auth) > 7 && auth[:7] == "Bearer " {
token = auth[7:]
}
if u, ok := userStore[token]; ok {
c.Set("user", u)
}
c.Next()
})
// Vulnerable endpoint: validates token but does NOT verify resource ownership
r.GET("/vuln/apps/:appId/workflows", func(c *gin.Context) {
appId := c.Param("appId")
if a, ok := apps[appId]; ok {
// No ownership check; returns data for any appId to any authenticated user
c.JSON(http.StatusOK, gin.H{
"workflows": []string{"build", "train", "evaluate"},
"app": a.Name,
})
return
}
c.JSON(http.StatusNotFound, gin.H{ "error": "app not found" })
})
// Fixed endpoint: enforce object-level authorization by checking ownership
r.GET("/fix/apps/:appId/workflows", func(c *gin.Context) {
appId := c.Param("appId")
if a, ok := apps[appId]; ok {
val, exists := c.Get("user")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{ "error": "unauthorized" })
return
}
u := val.(User)
if a.TeamID != u.TeamID {
c.JSON(http.StatusForbidden, gin.H{ "error": "forbidden: app does not belong to your team" })
return
}
c.JSON(http.StatusOK, gin.H{
"workflows": []string{"build", "train", "evaluate"},
"app": a.Name,
})
return
}
c.JSON(http.StatusNotFound, gin.H{ "error": "app not found" })
})
r.Run(":8080")
}