Overview
Broken Object Level Authorization (BOLA) vulnerabilities occur when an API fails to enforce per-object access controls, allowing users to access resources they shouldn’t. CVE-2026-33428 illustrates this in Discourse: a non-staff user with elevated group membership could access deleted posts belonging to any user due to an overly broad authorization check on the deleted posts index endpoint. In Go applications using Gin, a similar pattern can manifest when a list (index) endpoint gates access with a general permission and then returns all matching resources (e.g., all deleted posts) regardless of ownership or specific object-level permissions. Such oversight can enable attackers to enumerate or retrieve data from other users simply by possessing a higher-level role or group, undermining data confidentiality and trust boundaries. This guide ties the concept to Go (Gin) implementations and provides concrete remediation patterns.
Affected Versions
Prior to 2026.3.0-latest.1, 2026.2.1, and 2026.1.2; patched in 2026.3.0-latest.1, 2026.2.1, and 2026.1.2.
Code Fix Example
Go (Gin) API Security Remediation
Vulnerable:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type User struct {
ID int64
IsStaff bool
Groups []string
}
type Post struct {
ID int64
OwnerID int64
Deleted bool
}
var posts = []Post{
{ID: 1, OwnerID: 1, Deleted: true},
{ID: 2, OwnerID: 2, Deleted: true},
{ID: 3, OwnerID: 1, Deleted: false},
}
func getUserFromContext(c *gin.Context) User { // mock authentication
return User{ID: 1, IsStaff: false, Groups: []string{"elevated"}}
}
func contains(slice []string, s string) bool {
for _, v := range slice {
if v == s { return true }
}
return false
}
// Vulnerable: authorization check is broad and returns all deleted posts if user is staff OR in elevated group
func getDeletedPostsVulnerable(c *gin.Context) {
user := getUserFromContext(c)
if user.IsStaff || contains(user.Groups, "elevated") {
// Returns all deleted posts regardless of owner
var res []Post
for _, p := range posts {
if p.Deleted {
res = append(res, p)
}
}
c.JSON(http.StatusOK, res)
return
}
c.Status(http.StatusForbidden)
}
// Fixed: enforce per-object authorization (only admins see all; others see only their own deleted posts)
func getDeletedPostsFixed(c *gin.Context) {
user := getUserFromContext(c)
if user.IsStaff {
var res []Post
for _, p := range posts {
if p.Deleted {
res = append(res, p)
}
}
c.JSON(http.StatusOK, res)
return
}
var own []Post
for _, p := range posts {
if p.Deleted && p.OwnerID == user.ID {
own = append(own, p)
}
}
c.JSON(http.StatusOK, own)
}
func main() {
r := gin.Default()
r.GET("/deleted-posts/vulnerable", getDeletedPostsVulnerable)
r.GET("/deleted-posts/fixed", getDeletedPostsFixed)
r.Run()
}