Backport SECVULN-44115: forbid merging SCIM entities via merge API into release/2.0.x+ent (#16323) (#16405)

* SECVULN-44115: forbid merging SCIM entities via merge API (#15980)

* forbid merging SCIM entities via merge API

* add changelog

* address feedback

* more and better tests

* use txn

* fix backport

* remove more tests that came from nowhere

---------

Co-authored-by: Bruno Oliveira de Souza <bruno.souza@hashicorp.com>
This commit is contained in:
Vault Automation 2026-07-13 15:44:42 -04:00 committed by GitHub
parent 6bdd9f387b
commit fcf18cbb96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 19 additions and 1 deletions

3
changelog/_15980.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:security
identity/scim (enterprise): The `identity/entity/merge` endpoint now rejects requests that involve any SCIM-managed entity, preventing privileged operators from bypassing SCIM ownership guardrails to transfer aliases, group memberships, or policies across SCIM boundaries.
```

View file

@ -285,11 +285,26 @@ func (i *IdentityStore) pathEntityMergeID() framework.OperationFunc {
txn := i.db.Txn(true)
defer txn.Abort()
toEntity, err := i.MemDBEntityByID(toEntityID, true)
toEntity, err := i.MemDBEntityByIDInTxn(txn, toEntityID, true)
if err != nil {
return nil, err
}
// Merging SCIM-managed entities via the API is not allowed.
if toEntity != nil && toEntity.ScimClientID != "" {
return logical.ErrorResponse("SCIM-managed resources must be modified through SCIM, cannot target to_entity %s", toEntity.ID), logical.ErrPermissionDenied
}
for _, fromEntityID := range fromEntityIDs {
fromEntity, err := i.MemDBEntityByIDInTxn(txn, fromEntityID, false)
if err != nil {
return nil, err
}
// non-existent fromEntity validation is handled in mergeEntity
if fromEntity != nil && fromEntity.ScimClientID != "" {
return logical.ErrorResponse("SCIM-managed resources must be modified through SCIM, cannot target from_entity %s", fromEntity.ID), logical.ErrPermissionDenied
}
}
userErr, intErr, aliases := i.mergeEntity(ctx, txn, toEntity, fromEntityIDs, conflictingAliasIDsToKeep, force, false, false, true, false)
if userErr != nil {
// Not an error due to alias clash, return like normal