Repository navigation
Ingestors install the stored user on user sync, so role revocations take effect - #80
Merged
Merged
Conversation
The querier commits role removals before syncing, so the ingestor's receiver sees storage without the revoked roles and rejects the sync, leaving them live. These tests describe the reconcile step the receivers should share; the helper is a stub for now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The querier commits a role removal to storage before syncing it, so the ingestor's receiver re-read metadata that no longer held the roles, failed its manual-roles check with RolesManagedByOidc, and left the revoked roles live until restart. The other user receivers had the same shape: create replayed the request body, delete and add rejected syncs the live map was not ready for, and none held UPDATE_LOCK across the read, so late or out-of-order syncs could install older state. Every user receiver now takes UPDATE_LOCK, reads the tenant's metadata, installs the user as stored (removing it if storage no longer has it), which drops that user's sessions, and writes staging, logging failures. Request bodies are still parsed, so the wire format and routes are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ingestors now install what storage holds, so a deletion sync sent before the key left storage reinstalled it and left the revoked key usable. On a sync failure the local key is still kept so the caller can retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VibeSWE
commented
Oct 25, 2026
VibeSWE
left a comment
Author
There was a problem hiding this comment.
Adversarial review of PR #80 (at 662b45e4, fixes through 897e9edb)
One Sol 6.1 and one Opus 5.5 reviewer.
Both independently found one blocking regression, now fixed. delete_api_key sent the deletion sync before removing the key from storage. Ingestors now install what storage holds, so they reinstalled the key and returned 200, and a revoked API key stayed usable on every ingestor.
- Fix (
c68fcd94): the key is removed from storage first, then synced. On a sync failure the local key is still kept, so a retried DELETE finds it and syncs again; storage no longer has it, so a restart drops it too. - Other senders checked: every other sender (user create/delete, role add/remove, password, OIDC login, API-key create) already commits before syncing.
| Severity | Finding | Outcome |
|---|---|---|
| High | API-key delete synced before committing (above) | Fixed in c68fcd94 |
| Medium | Tests cover apply_stored_user, not the handlers; missing API-key and cross-tenant cases |
Added tests for API-key install/remove, and for deleting a userid in one tenant leaving another tenant's alone. The handlers read storage, which tests can't reach |
| Low (pre-existing) | Deleting a user left them in live USER_GROUPS membership |
Fixed in dd3275de, with a red test first |
| Low | Every user sync now drops that user's ingestor sessions | Accepted: ingestors serve no UI, and Basic/API-key sessions rebuild on the next request |
| Low | The module comment claimed the querier syncs after releasing its lock | Reworded |
| Nit | Handler comments described the old body-driven behaviour | Updated |
Also checked:
- OIDC: no live-only grants are lost; login persists the effective roles and
role_grants, and only bearer tokens are memory-only. - Tenants: tenant handling is unchanged.
- Locks: lock order holds, and no guard is held across an
.await. - Mounting and mixed versions: the receivers are mounted only on ingestors; mixed-version clusters behave as before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
High (pre-existing): revoking roles from a user never took effect on ingestors.
role/sync/removereceiver re-read storage, where the roles were already gone. Itsis_subset(manual_roles)check then returnedRolesManagedByOidcbeforeUsers.remove_rolesran.All five user sync receivers in
ingest/ingestor_rbac.rshad the same flaw: each acted on the request instead of what storage holds.role/sync/removerole/sync/addPOST /user/{id}/syncUPDATE_LOCKDELETE /user/{id}/syncUPDATE_LOCK, so two quick resets could leave the older hashFix (same approach as #78):
sync_stored_user, which takesUPDATE_LOCK, re-reads the tenant's metadata and callsapply_stored_user.apply_stored_userinstalls the stored user and drops their sessions, or removes the user if storage no longer has them. It is idempotent.let _(a follow-up deferred from fix(rbac): resolve lock-order deadlocks between sessions, users, groups and roles #31).Sender fix found in review:
delete_api_keysynced the deletion before removing the key from storage. With storage-driven receivers, that would have reinstalled the key on every ingestor. It now commits first. On a sync failure it still returnsRevocationSyncFailedand keeps the local key, so a retry re-syncs. Every other sender already committed first.Deleting a user also removes them from live group membership on the ingestor.
Behaviour changes:
Tests
Tests against
apply_stored_user, committed red against a no-op stub:The async handlers (lock, read, apply) can't be unit-tested, because
get_metadatainitialisesPARSEABLE.Verification
cargo fmt --check✅,cargo clippy -- -D warnings✅cargo test --lib: 1026 passed, 0 failed, 2 ignoredNot in this PR
The querier still doesn't retry a failed sync, so an ingestor that misses one stays stale until the next sync for that user or a restart.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.