Followers return stale entity group membership when using cross-namespace identities and standby reads
Describe the bug
When using unsafe_cross_namespace_identity=true with standby reads enabled (in a multi-node, Raft-backed cluster), creating even a few dozen groups with cross-namespace membership can cause the leader and followers to return different sets of groups for an entity.
The difference in group membership is not merely cosmetic -- the followers will enforce request authorization based on policies in this incorrect set of groups, returning 403s for requests the leader would have allowed (having the correct view of groups).
To Reproduce
The following POC reproduces the bug reliably via Docker Compose. It consists of only two files:
Bringing up the POC and looking at the logs of the poc service shows the divergence:
openbao-inconsistency-poc-poc-1 | REPRODUCED — at least one follower's entity has fewer group edges than the leader. Counts:
openbao-inconsistency-poc-poc-1 | Node node1: {"direct":30,"inherited":30,"total":60}
openbao-inconsistency-poc-poc-1 | Node node2: {"direct":28,"inherited":23,"total":51}
openbao-inconsistency-poc-poc-1 | Node node3: {"direct":28,"inherited":23,"total":51}These counts represent the numbers of groups reported for the same entity by each of the nodes (node1 being the leader).
Expected behavior It's expected that the followers would agree with the leader about group membership for all entities.
Environment:
- OpenBao Server Version: OpenBao v2.6.1 (ba7ad8861d0578cd4da4f7b9e5a6756d30484f8f), committed 2026-07-22T14:22:20Z
- OpenBao CLI Version (retrieve with
bao version): n/a - Server Operating System/Architecture: Docker in Linux
OpenBao server configuration file(s):
The POC creates a different file per server, but the leader has:
disable_mlock = true
ui = false
disable_standby_reads = false
unsafe_cross_namespace_identity = true
storage "raft" {
path = "/openbao/file"
node_id = "node1"
retry_join { leader_api_addr = "http://node1:8200" }
retry_join { leader_api_addr = "http://node2:8200" }
retry_join { leader_api_addr = "http://node3:8200" }
}
listener "tcp" {
address = "0.0.0.0:8200"
cluster_address = "0.0.0.0:8201"
tls_disable = true
}
api_addr = "http://node1:8200"
cluster_addr = "http://node1:8201"Note that the POC also allows disabling standby reads in order to confirm that that change removes the divergence.
Source: openbao/openbao