awslabs / awslabs/cli-agent-orchestrator
[Feat] Tenant administration: roles, member management, and revocable sessions
- Dominant language
- Python
- Stars
- 1.3k
- Forks
- 267
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 70
Description
[Feat] Tenant administration: roles, member management, and revocable sessions
Part of #777 (CAO 3.0). Co-delivered with #774 against the agreed tenant/identity contract from #778 and the shared migration in #775. Identity, administration and revocation integrate together; completion of all of #774 is not a prerequisite for starting this issue.
## In plain terms
Once a team shares one CAO, somebody has to be in charge of it. Who can add a colleague? Who removes someone who has left? Who can shut down a runaway agent that isn't theirs?
Today the answer is nobody, because CAO has no users. There is an `admin` scope on API tokens, but that describes what *a token* may do — it does not describe a *person*, and there is no user record for it to attach to.
Without this, a shared CAO can be signed into but not run.
## What this delivers
**Two roles to begin with: administrator and member.** Resisting a full permission system in the first version is deliberate — two roles cover the real needs and can be extended later. Inventing a fine-grained permission model before anyone has asked for one is how this gets expensive.
- A **member** can create, inspect, run, stop, edit and delete their own work where the resource supports those operations. In 3.0 that work is **private**: #780 enforces this on APIs/imports as well as the interface. Sharing user-owned work between members arrives after 3.0.
- An **administrator** additionally manages who is in the tenant, and can intervene in anything inside it — including stopping another person's runaway agent.
Roles belong to the person's **membership in a tenant**. Being an administrator in one tenant grants nothing in another. These roles are distinct from token scopes and agent-profile tool-access roles. A tenant administrator is not the installation operator: global paths, platform credentials and deployment configuration remain operator-controlled under #778.
Define the action/ownership matrix with #774 rather than mapping "member" to today's write scope and leaving every guard unchanged. Deleting one's own session, terminal, flow or workflow spec currently hits an admin-only guard (`api/main.py:3134`, `:6628`, `:7110`, `:4367`). Those are ordinary owner operations in the new model, not a reason to give every member global administration.
**Member management.** Invite, remove, and change a person's role. Removing someone must have a defined answer for what happens to their work — it should not silently vanish, and it should not remain reachable by them.
The first administrator comes from #778's operator-controlled setup, not an invitation issued by an administrator who does not yet exist. Prevent concurrent removals/demotions from leaving a tenant without an active administrator, and document operator-controlled recovery. A valid IdP login alone must not recreate a removed membership or undo a deliberate role change.
In 3.0, user-owned work remains private. On removal it keeps its owner and is retained; access through that tenant membership ends, while an authorized tenant administrator can inspect it and complete cleanup. Other tenant memberships are unaffected. Ownership transfer and the fate of deliberately shared work remain after 3.0.
**Sessions that can be ended.** If someone leaves the company, their access has to actually stop. This is the part that's easy to get wrong, and the sources genuinely pull in two directions:
- AWS SaaS guidance says put tenant and role into the token's claims, to avoid a lookup on every request — *"without requiring a roundtrip to another service… Managing this context through another service adds latency and often creates bottlenecks."*
- OWASP ASVS 8.3.2 says authorisation changes *"are applied immediately"*, and notes that where they can't be — *"such as when relying on data in self-contained tokens"* — there must be compensating controls.
Both are right. The resolution has to be explicit rather than accidental: **short-lived tokens plus a way to revoke**. Pick the lifetime deliberately and write down why.
Revocation must reach more than the next HTTP request:
- End the affected sessions/refresh authority and delegated data/tool grants, and close affected browser WebSocket/SSE subscriptions. Check the current authority for subsequent actions and reconnections; do not wait for a signed token to expire.
- Prevent new queued, scheduled or resumed work from starting under the removed membership. Persist enough tenant/owner context to apply this after a server restart, not just while the original request is in memory.
- Request cancellation of active work and report the actual outcome using #745's requested-versus-stopped contract. Retain the bounded server/runtime control path needed to stop execution and preserve final diagnostics after human/tool access is revoked. An unreachable worker is not proof of termination, and cancellation cannot undo external effects already made.
- Apply role changes to the permissions they remove, including existing channels and delegated operations. Do not reinstate old authority from cached claims.
A browser disconnect closes the channel, not the CAO login session. Sign-out or expiry ends that session's access. Neither is member removal, and neither silently cancels independently authorized background work.
The current terminal WebSocket checks scopes at connection time (`api/main.py:6850-6877`) and then forwards input/output in long-running loops (`:6960-7019`). Adding a membership check only to new HTTP calls would miss that path. The [OWASP WebSocket guidance](https://cheatsheetseries.owasp.org/cheatsheets/WebSocket_Security_Cheat_Sheet.html) explicitly covers session expiry, closing connections on logout and message-level authorization.
**The local laptop has none of this.** One person, implicitly the administrator of their tenant of one, with nothing to manage and no sign-in.
## Acceptance criteria
- [ ] Administrator and member roles attach to a person's tenant membership, not merely to a token or agent profile. The same identity may have different permissions in different tenants.
- [ ] First-admin setup is integrated with #778; concurrent demotion/removal cannot eliminate the last active administrator, and recovery is operator-controlled.
- [ ] An administrator can invite, remove, and re-role people within their own tenant, and **only** their own tenant.
- [ ] Removal ends the affected tenant access across HTTP, refresh/session credentials, live WebSocket/SSE connections and delegated tools; retained work keeps its owner and another tenant membership is unaffected.
- [ ] Queued/scheduled/resumed work cannot start for a removed membership, including after restart. Active work is cancelled with confirmed, failed or unknown outcomes; authorized control/cleanup still works after delegated access is revoked.
- [ ] A still-valid IdP token or group claim cannot silently recreate a removed membership or reverse an administrator's role change.
- [ ] Token lifetime and the revocation mechanism are chosen deliberately and the reasoning is recorded.
- [ ] An administrator can stop another member's agent or terminal within their tenant.
- [ ] A member can complete the supported create/run/stop/edit/delete lifecycle of their own work without gaining tenant-management or installation-wide permissions. Test owner, same-tenant administrator and unrelated-tenant callers against the same action matrix.
- [ ] Neither tenant role can change operator-only installation settings through a broad legacy scope.
- [ ] A transport disconnect preserves a still-valid login for reconnect. Sign-out/session expiry ends the affected session's access; neither silently cancels independently authorized jobs. Membership removal follows the separate policy above.
- [ ] On a laptop, none of this appears and nothing needs configuring.
## Evidence
| Claim | Where |
| --- | --- |
| `admin` is a token scope, not a person | `security/auth.py:44-46` |
| No user, role, or session table exists | 14 tables, none identity-related |
| Token-claims vs immediate-revocation tension | AWS SaaS Lens; OWASP ASVS 8.3.2, 7.4.1 |
All repository observations verified on `main` at `29b235cf62ed0f9d624bc9ad9afce09ab72f8ddf`.
Contributor guide
Research direction
Start with the tenant and identity contracts in #778, #774, and #775, then inspect security/auth.py:44-46 and the terminal WebSocket paths in api/main.py:6850-6877 and :6960-7019. Review #745 for cancellation outcomes and the stated AWS SaaS and OWASP guidance before choosing the session and revocation design. Done means all acceptance criteria pass, including tenant-scoped roles, immediate access revocation, queued-work handling, cancellation outcomes, and unchanged laptop behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, authentication, authorization, backend, distributed-systems, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100