Broken Object Level Authorization

Broken Object Level Authorization in Go (Gin) [May 2026] [GHSA-rwww-x45w-p52w]

[Updated May 2026] Updated GHSA-rwww-x45w-p52w

Overview

Broken object level authorization (BOLOA) is a common API vulnerability where an attacker can access resources they do not own by manipulating object IDs. In Go services built with the Gin framework, endpoints frequently accept IDs via path parameters and return data or perform actions without strict ownership checks. The real-world impact includes data leakage, privacy violations, and potential regulatory risk when sensitive resources are exposed to unauthorized users. BOLOA manifests in Gin when authorization checks are performed after loading the object, rely on unsafe constraints, or are implemented per endpoint rather than as a centralized policy. Patterns include fetching by id and then comparing owner_id to the current user, or issuing queries that do not filter by owner or tenant, enabling attackers to enumerate and access other users' resources. Mitigation requires consistent, early ownership validation, centralizing authorization logic, and leveraging database constraints or row-level security where available. Enforce per-user or per-tenant access at the boundary, and write tests that cover forbidden access and positive access across all object types.

Code Fix Example

Go (Gin) API Security Remediation
package main

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

type Resource struct {
  ID int64
  OwnerID int64
  Data string
}

var resources = []Resource{
  {ID: 1, OwnerID: 100, Data: "secret1"},
  {ID: 2, OwnerID: 200, Data: "secret2"},
}

func getResourceByID(id int64) *Resource {
  for i := range resources {
    if resources[i].ID == id {
      return &resources[i]
    }
  }
  return nil
}

func getUserIDFromHeader(c *gin.Context) int64 {
  if v := c.GetHeader(`X-User-ID`); v != "" {
    if n, err := strconv.ParseInt(v, 10, 64); err == nil {
      return n
    }
  }
  return 0
}

func main() {
  r := gin.Default()

  // Vulnerable: no ownership check
  r.GET(`/vulnerable/resources/:id`, func(c *gin.Context){
    id, _ := strconv.ParseInt(c.Param(`id`), 10, 64)
    res := getResourceByID(id)
    if res == nil {
      c.JSON(http.StatusNotFound, gin.H{`error`: `not found`})
      return
    }
    c.JSON(http.StatusOK, gin.H{`id`: res.ID, `data`: res.Data})
  })

  // Fixed: verify ownership before serving
  r.GET(`/fixed/resources/:id`, func(c *gin.Context){
    userID := getUserIDFromHeader(c)
    id, _ := strconv.ParseInt(c.Param(`id`), 10, 64)
    res := getResourceByID(id)
    if res == nil {
      c.JSON(http.StatusNotFound, gin.H{`error`: `not found`})
      return
    }
    if res.OwnerID != userID {
      c.JSON(http.StatusForbidden, gin.H{`error`: `forbidden`})
      return
    }
    c.JSON(http.StatusOK, gin.H{`id`: res.ID, `data`: res.Data})
  })

  r.Run(`:8080`)
}

CVE References

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