Broken Object Level Authorization

Broken Object Level Authorization in Go (Gin) [CVE-2026-41131]

[Fixed 2026-04] Updated CVE-2026-41131

Overview

OpenFGA CVE-2026-41131 describes a caching-related flaw that can enable Broken Object Level Authorization (BOLA) when using condition-based relations with caching enabled. In pre-attack scenarios, two different check requests can end up producing the same cache key, causing the system to reuse an earlier cached authorization result for a subsequent, distinct resource. This can allow a user to gain access to objects they should not be authorized to access. The weakness maps to CWE-706 (When access is granted too broadly due to misapplied security checks) and CWE-863 (Incorrect implementation of access control logic). The vulnerability is fixed in OpenFGA v1.14.1, and applies to deployments that rely on OpenFGA for authorization decisions within Go services (including those built with Gin) while leveraging caching for performance. In practice, a Go (Gin) service that calls OpenFGA and caches authorization decisions can inadvertently reuse a cached result across different resources if the cache key neglects the object identity. An attacker could craft requests to alternating resources under the same user and action, observe a cache miss/miss pattern, and then rely on a reused positive decision for a resource that should be denied. This is a classic OBLA scenario: the authorization decision is not properly scoped to the specific object, so a user could access another object with the same permission relationship. Remediating this in a Go (Gin) application involves both upgrading to the patched engine and ensuring your own in-process or distributed cache scopes decisions per object. Key mitigations include including the resource/object identifier in the cache key, avoiding cross-resource cache leakage for authorization results, configuring appropriate TTLs, and validating changes with targeted tests. The CVE references should be used as the basis for verification and regression testing, and you should upgrade to OpenFGA v1.14.1 or later to apply the official fix.

Affected Versions

OpenFGA <= 1.14.0; Fixed in OpenFGA v1.14.1

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
  "sync"
  "net/http"
  "github.com/gin-gonic/gin"
)

// SafeCache is a tiny in-process cache for demonstration purposes.
type SafeCache struct {
  mu   sync.RWMutex
  data map[string]bool
}

func NewSafeCache() *SafeCache {
  return &SafeCache{data: make(map[string]bool)}
}

func (c *SafeCache) Get(key string) (bool, bool) {
  c.mu.RLock()
  defer c.mu.RUnlock()
  val, ok := c.data[key]
  return val, ok
}

func (c *SafeCache) Set(key string, val bool) {
  c.mu.Lock()
  c.data[key] = val
  c.mu.Unlock()
}

// OpenFGACheck simulates a call to OpenFGA's authorization decision.
// In real code, this would be a network call to the OpenFGA SDK/service.
func OpenFGACheck(user, resource, action string) bool {
  // Simple simulated policy: alice can read document:123; admins can read anything
  if user == "alice" && resource == "document:123" && action == "read" {
    return true
  }
  if user == "admin" && action == "read" {
    return true
  }
  return false
}

// Vulnerable pattern: cache key is user + action only; resource is not part of the key
// This can cause cross-resource cache hits (OBLA).
func vulnerableHandler(cache *SafeCache) gin.HandlerFunc {
  return func(c *gin.Context) {
    user := c.Query("user")
    resource := c.Query("resource")
    action := "read"

    key := user + ":" + action // cache key ignores resource/object identity
    if v, ok := cache.Get(key); ok {
      c.JSON(http.StatusOK, gin.H{"allowed": v, "cache": "hit (vuln)"})
      return
    }

    allowed := OpenFGACheck(user, resource, action)
    cache.Set(key, allowed)
    c.JSON(http.StatusOK, gin.H{"allowed": allowed, "cache": "miss (vuln)"})
  }
}

// Fixed pattern: include the resource/object identifier in the cache key
// This isolates decisions per object and prevents cross-resource leakage.
func fixedHandler(cache *SafeCache) gin.HandlerFunc {
  return func(c *gin.Context) {
    user := c.Query("user")
    resource := c.Query("resource")
    action := "read"

    key := user + ":" + resource + ":" + action // cache key includes resource/object identity
    if v, ok := cache.Get(key); ok {
      c.JSON(http.StatusOK, gin.H{"allowed": v, "cache": "hit (fixed)"})
      return
    }

    allowed := OpenFGACheck(user, resource, action)
    cache.Set(key, allowed)
    c.JSON(http.StatusOK, gin.H{"allowed": allowed, "cache": "miss (fixed)"})
  }
}

func main() {
  r := gin.Default()
  vulnCache := NewSafeCache()
  fixedCache := NewSafeCache()

  // Endpoints for demonstration
  r.GET("/vuln", vulnerableHandler(vulnCache))
  r.GET("/fixed", fixedHandler(fixedCache))

  // Simple health check
  r.GET("/health", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"status": "ok"})
  })

  // Run on port 8080
  _ = r.Run(":8080")
}

CVE References

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