Broken Object Property Level Authorization

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

[Updated month year] Updated CVE-2026-39412

Overview

Broken Object Property Level Authorization vulnerabilities occur when an API or template output reveals more object properties than intended to a caller. The CVE-2026-39412 case in LiquidJS shows how bypassing a boundary (ownPropertyOnly) enabled reading prototype-inherited properties via a sorting side-channel, leaking keys and tokens. Although this CVE is in a JavaScript template engine, the same risk exists in any system that does not strictly bound which fields are exposed in an API or template output. In Go with Gin, the equivalent vulnerability appears when a handler or service returns internal models directly or uses dynamic projection without enforcing proper access control. An attacker or tenant could observe or infer fields they shouldn't see, such as passwords, API keys, tokens, or admin flags, simply by calling an endpoint that marshals a struct or maps to a response. This is effectively a Broken Object Property Level Authorization concern: the boundary around which properties are exposed is not enforced per user/role or per tenant, enabling information disclosure. Remediation in Go (Gin) includes: defining explicit API DTOs; always projecting internal models to DTOs before JSON marshalling; enforcing per-endpoint and per-field authorization; masking or omitting sensitive fields using json tags or by not including them in the DTO; adding tests; optionally using an authorization library to codify who can see which properties.

Affected Versions

LiquidJS <= 10.25.3 (CVE-2026-39412)

Code Fix Example

Go (Gin) API Security Remediation
VULNERABLE PATTERN (Go, Gin)
package main

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

type User struct {
  ID           string `json:\"id\"`
  Username     string `json:\"username\"`
  Email        string `json:\"email\"`
  PasswordHash string `json:\"password_hash\"`
  ApiKey       string `json:\"api_key\"`
}

func GetUserVulnerable(c *gin.Context) {
  id := c.Param(\"id\")
  // Simulated DB fetch; in real code, pull from repository
  u := User{ID: id, Username: \"alice\", Email: \"[email protected]\", PasswordHash: \"secret\", ApiKey: \"apikey\"}
  // Vulnerable: returns internal model with sensitive fields
  c.JSON(http.StatusOK, u)
}

// FIXED PATTERN (Go, Gin)
type PublicUser struct {
  ID       string `json:\"id\"`
  Username string `json:\"username\"`
  Email    string `json:\"email\"`
}

func GetUserFixed(c *gin.Context) {
  id := c.Param(\"id\")
  u := User{ID: id, Username: \"alice\", Email: \"[email protected]\", PasswordHash: \"secret\", ApiKey: \"apikey\"}
  // Explicit projection to a safe DTO
  pu := PublicUser{ID: u.ID, Username: u.Username, Email: u.Email}
  c.JSON(http.StatusOK, pu)
}

func main() {
  r := gin.Default()
  r.GET(\"/user/:id\", GetUserVulnerable)
  r.GET(\"/user_public/:id\", GetUserFixed)
  _ = r
}

CVE References

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