Broken Object Level Authorization

Broken Object Level Authorization in Go Gin [February 2026] [CVE-2026-29044]

[Fixed February 2026] Updated CVE-2026-29044

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)
  }
}

CVE References

Choose which optional cookies to allow. You can change this any time.