CVE-2025-30086
Description
CNCF Harbor 2.13.x before 2.13.1 and 2.12.x before 2.12.4 allows information disclosure by administrators who can exploit an ORM Leak present in the /api/v2.0/users endpoint to leak users' password hash and salt values. The q URL parameter allows a user to filter users by any column, and filter password=~ could be abused to leak out a user's password hash character by character. An attacker with administrator access could exploit this to leak highly sensitive information stored in the Harbor database. All endpoints that support the q URL parameter are vulnerable to this ORM leak attack.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CNCF Harbor 2.12.x to 2.13.0 allows admin attackers to leak password hashes via an ORM leak in the /api/v2.0/users endpoint.
Vulnerability
Description
CVE-2025-30086 is an ORM Leak vulnerability in CNCF Harbor versions 2.13.x before 2.13.1 and 2.12.x before 2.12.4. The underlying issue resides in the /api/v2.0/users endpoint, where the q URL parameter allows filtering users by any column. An attacker with administrator access can abuse this by using a filter such as password=~ to leak a user's password hash and salt values character by character. This class of vulnerability, termed ORM Leak, arises when user-supplied input is passed unsanitized to an ORM's filtering methods, allowing an attacker to extract sensitive data through relational filtering attacks [1][2].
Exploitation
Exploitation requires administrator-level access to the Harbor instance. Using the vulnerable q parameter, an admin can perform a character-by-character extraction of password hashes and salts from the database, bypassing any intended access controls on sensitive fields. The vulnerability affects all endpoints that support the q URL parameter, making it a systemic issue within the affected versions [1][2].
Impact
Successful exploitation allows an attacker with admin privileges to obtain highly sensitive credential material (password hashes and salts) for any user. This information could be used offline in brute-force or dictionary attacks to recover plaintext passwords, potentially leading to account takeover and further compromise of the Harbor registry [1][2].
Mitigation
Harbor has fixed this issue in versions 2.13.1 and 2.12.4. Administrators are strongly advised to upgrade to these patched releases or later. No workaround is mentioned in the official advisories. The CVE is not yet listed in CISA's Known Exploited Vulnerabilities catalog [3][4].
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/goharbor/harborGo | >= 2.13.0, < 2.13.1 | 2.13.1 |
github.com/goharbor/harborGo | >= 2.4.0-rc1.1, < 2.12.4 | 2.12.4 |
github.com/goharbor/harborGo | < 2.4.0-rc1.0.20250331071157-dce7d9f5cffb | 2.4.0-rc1.0.20250331071157-dce7d9f5cffb |
Affected products
2Patches
36b4981d4dd469384ed0e47b0dce7d9f5cffbfix orm filterable issue (#21797)
7 files changed · +16 −9
src/common/models/oidc_user.go+1 −1 modified@@ -23,7 +23,7 @@ type OIDCUser struct { ID int64 `orm:"pk;auto;column(id)" json:"id"` UserID int `orm:"column(user_id)" json:"user_id"` // encrypted secret - Secret string `orm:"column(secret)" json:"-"` + Secret string `orm:"column(secret)" filter:"false" json:"-"` // secret in plain text PlainSecret string `orm:"-" json:"secret"` SubIss string `orm:"column(subiss)" json:"subiss"`
src/lib/orm/metadata.go+4 −1 modified@@ -45,7 +45,10 @@ type metadata struct { func (m *metadata) Filterable(key string) (*key, bool) { k, exist := m.Keys[key] - return k, exist + if !exist { + return nil, false + } + return k, k.Filterable } func (m *metadata) Sortable(key string) bool {
src/lib/orm/metadata_test.go+4 −0 modified@@ -66,6 +66,8 @@ func TestParseQueryObject(t *testing.T) { key, exist := metadata.Keys["Field2"] require.True(exist) assert.Equal("Field2", key.Name) + _, filterable := metadata.Filterable("Field2") + assert.False(filterable) assert.False(key.Filterable) assert.True(key.Sortable) @@ -78,6 +80,8 @@ func TestParseQueryObject(t *testing.T) { key, exist = metadata.Keys["Field3"] require.True(exist) assert.Equal("Field3", key.Name) + _, filterable = metadata.Filterable("Field2") + assert.False(filterable) assert.True(key.Filterable) assert.False(key.Sortable)
src/lib/orm/query.go+1 −1 modified@@ -162,7 +162,7 @@ func setFilters(ctx context.Context, qs orm.QuerySeter, query *q.Query, meta *me // e.g. "ArtifactDigest" or the snake case format "artifact_digest" should be used instead: // https://github.com/goharbor/harbor/blob/v2.2.0/src/controller/blob/controller.go#L233 mk, filterable = meta.Filterable(snakeCase(k)) - if !filterable { + if mk == nil || !filterable { continue } }
src/pkg/reg/dao/model.go+2 −2 modified@@ -30,8 +30,8 @@ type Registry struct { URL string `orm:"column(url)"` Name string `orm:"column(name)"` CredentialType string `orm:"column(credential_type);default(basic)"` - AccessKey string `orm:"column(access_key)"` - AccessSecret string `orm:"column(access_secret)"` + AccessKey string `orm:"column(access_key)" filter:"false"` + AccessSecret string `orm:"column(access_secret)" filter:"false"` Type string `orm:"column(type)"` Insecure bool `orm:"column(insecure)"` Description string `orm:"column(description)"`
src/pkg/robot/model/model.go+2 −2 modified@@ -32,8 +32,8 @@ type Robot struct { ID int64 `orm:"pk;auto;column(id)" json:"id"` Name string `orm:"column(name)" json:"name" sort:"default"` Description string `orm:"column(description)" json:"description"` - Secret string `orm:"column(secret)" json:"secret"` - Salt string `orm:"column(salt)" json:"-"` + Secret string `orm:"column(secret)" filter:"false" json:"secret"` + Salt string `orm:"column(salt)" filter:"false" json:"-"` Duration int64 `orm:"column(duration)" json:"duration"` ProjectID int64 `orm:"column(project_id)" json:"project_id"` ExpiresAt int64 `orm:"column(expiresat)" json:"expires_at"`
src/pkg/user/dao/user.go+2 −2 modified@@ -32,14 +32,14 @@ type User struct { // Email defined as sql.NullString because sometimes email is missing in LDAP/OIDC auth, // set it to null to avoid unique constraint check Email sql.NullString `orm:"column(email)" json:"email"` - Password string `orm:"column(password)" json:"password"` + Password string `orm:"column(password)" filter:"false" json:"password"` PasswordVersion string `orm:"column(password_version)" json:"password_version"` Realname string `orm:"column(realname)" json:"realname"` Comment string `orm:"column(comment)" json:"comment"` Deleted bool `orm:"column(deleted)" json:"deleted"` SysAdminFlag bool `orm:"column(sysadmin_flag)" json:"sysadmin_flag"` ResetUUID string `orm:"column(reset_uuid)" json:"reset_uuid"` - Salt string `orm:"column(salt)" json:"-"` + Salt string `orm:"column(salt)" filter:"false" json:"-"` CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"` UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"` }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-h27m-3qw8-3pw8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-30086ghsaADVISORY
- github.com/goharbor/harbor/commit/dce7d9f5cffbd0d0c5d27e7a2f816f65a930702cghsaWEB
- github.com/goharbor/harbor/security/advisories/GHSA-h27m-3qw8-3pw8nvdWEB
- goharbor.io/blogghsaWEB
- www.elttam.com/blog/plormbing-your-django-ormghsaWEB
- goharbor.io/blog/nvd
- www.elttam.com/blog/plormbing-your-django-orm/nvd
News mentions
0No linked articles in our index yet.