Broken Object Level Authorization

Broken Object Level Authorization in Go Gin [Jun 2026] [CVE-2026-32911]

[Updated Jun 2026] Updated CVE-2026-32911

Overview

CVE-2026-32911 describes an authorization bypass in OpenClaw versions 2026.2.22 and 2026.2.23 where a dmPolicy set to allowlist with an empty allowedUserIds can result in an open gate to dispatch unauthorized messages to downstream agents and tools. This is a classic Broken Object Level Authorization (BOLA) flaw (CWE-863) that can let attackers with limited or no resource ownership access act on protected objects. The impact is broader when such policy checks are embedded in plugins or modules that handle inter-service messaging, effectively undermining per-object security guarantees in a microservice or plugin-rich Go (Gin) environment.

Affected Versions

OpenClaw 2026.2.22-2026.2.23

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type Policy struct {
  Policy         string
  AllowedUserIDs []string
}

func getUserID(c *gin.Context) string {
  if v, ok := c.Get("userID"); ok {
    if s, ok := v.(string); ok {
      return s
    }
  }
  return ""
}

// Vulnerable: empty allowlist path grants access (explicit bypass)
func vulnerableAuthorize(p Policy, userID string) bool {
  if p.Policy == "allowlist" {
    if len(p.AllowedUserIDs) == 0 {
      // Vulnerability: empty allowlist opens access
      return true
    }
    for _, id := range p.AllowedUserIDs {
      if id == userID {
        return true
      }
    }
    return false
  }
  return false
}

// Fixed: empty allowlist must deny by default
func fixedAuthorize(p Policy, userID string) bool {
  if p.Policy == "allowlist" {
    if len(p.AllowedUserIDs) == 0 {
      // Deny when allowlist is empty
      return false
    }
    for _, id := range p.AllowedUserIDs {
      if id == userID {
        return true
      }
    }
    return false
  }
  return false
}

// Vulnerable route demonstrating the bypass
func vulnerableHandler(c *gin.Context) {
  userID := getUserID(c)
  p := Policy{Policy: "allowlist", AllowedUserIDs: []string{}} // empty allowlist
  if !vulnerableAuthorize(p, userID) {
    c.AbortWithStatus(http.StatusForbidden)
    return
  }
  c.String(http.StatusOK, "VULNERABLE: Access granted to resource %s", c.Param("id"))
}

// Fixed route demonstrating proper guard
func fixedHandler(c *gin.Context) {
  userID := getUserID(c)
  p := Policy{Policy: "allowlist", AllowedUserIDs: []string{}} // empty allowlist
  if !fixedAuthorize(p, userID) {
    c.AbortWithStatus(http.StatusForbidden)
    return
  }
  c.String(http.StatusOK, "FIXED: Access granted to resource %s", c.Param("id"))
}

func main() {
  r := gin.Default()
  // Demo: inject a known user; in real apps, use proper authentication middleware
  r.Use(func(c *gin.Context) {
    c.Set("userID", "alice")
    c.Next()
  })

  r.GET("/vuln/doc/:id", vulnerableHandler)
  r.GET("/fix/doc/:id", fixedHandler)

  r.Run(":8080")
}

CVE References

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