Overview
The CVE-2026-29044 case demonstrates a broken object level authorization pattern where an authorization withdrawal can occur before the TransactionStarted event in a charging session. In EVerest, this path led to transaction_active being set to false while the system failed to actually stop ongoing charging, allowing charging to continue despite the withdrawal. This highlights a broader class of authorization flaws where timing or event-ordering gaps allow a user or attacker to exercise control over a resource without a guaranteed stop or state transition. The patch released in 2026.02.0 fixed this by ensuring that the stop action is invoked as part of the withdrawal flow, even when the TransactionStarted event has not yet occurred. This guide maps that remediation to Go microservice code using the Gin framework, illustrating how to prevent similar gaps in your own Go services. CWE-863 (Broken Object Level Authorization) captures the underlying risk: insufficient enforcement of per-object permissions and state transitions, leading to authorization bypass through race conditions or misordered events in Go (Gin) services.
Affected Versions
Pre-2026.02.0 (prior to 2026.02.0). The patch introduced in 2026.02.0 fixed this issue.
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
)
type ChargingSession struct {
ID string
TransactionStarted bool
TransactionActive bool
Stopped bool
Deauthorized bool
}
var sessions = map[string]*ChargingSession{
"sess-1": {ID: "sess-1", TransactionStarted: false, TransactionActive: false},
}
func getSession(id string) *ChargingSession {
if s, ok := sessions[id]; ok {
return s
}
s := &ChargingSession{ID: id}
sessions[id] = s
return s
}
func StopTransaction(s *ChargingSession) {
s.Stopped = true
s.TransactionActive = false
s.TransactionStarted = false
log.Printf("StopTransaction called for %s", s.ID)
}
func deauthorize(s *ChargingSession) {
s.Deauthorized = true
log.Printf("deauthorize called for %s", s.ID)
}
// Vulnerable pattern: withdraw before TransactionStarted can lead to no StopTransaction even
// though deauthorization is processed. This mimics the CVE behavior where the stop path isn't invoked.
func withdrawAuthorizationVulnerable(c *gin.Context) {
id := c.Param("id")
s := getSession(id)
// Simulated pre-TransactionStarted withdrawal path
if !s.TransactionStarted {
s.TransactionActive = false
log.Printf("VULN: withdrawal before TransactionStarted for %s", id)
deauthorize(s)
c.JSON(http.StatusOK, gin.H{"session": id, "status": "withdrawn (vuln)", "Stopped": s.Stopped, "Deauthorized": s.Deauthorized})
return
}
StopTransaction(s)
deauthorize(s)
c.JSON(http.StatusOK, gin.H{"session": id, "status": "withdrawn", "Stopped": s.Stopped, "Deauthorized": s.Deauthorized})
}
// Fixed pattern: even if withdrawal occurs before TransactionStarted, we still perform StopTransaction
// to ensure the hardware/session is actually stopped before deauthorization.
func withdrawAuthorizationFixed(c *gin.Context) {
id := c.Param("id")
s := getSession(id)
if !s.TransactionStarted {
// FIX: ensure stop path is invoked even when the transaction hasn't started yet
StopTransaction(s)
deauthorize(s)
c.JSON(http.StatusOK, gin.H{"session": id, "status": "withdrawn (fixed)", "Stopped": s.Stopped, "Deauthorized": s.Deauthorized})
return
}
StopTransaction(s)
deauthorize(s)
c.JSON(http.StatusOK, gin.H{"session": id, "status": "withdrawn", "Stopped": s.Stopped, "Deauthorized": s.Deauthorized})
}
func main() {
r := gin.Default()
r.POST("/vuln/withdraw/:id", withdrawAuthorizationVulnerable)
r.POST("/fix/withdraw/:id", withdrawAuthorizationFixed)
log.Println("Server listening on :8080")
if err := r.Run(":8080"); err != nil {
log.Fatalf("failed to run server: %v", err)
}
}