RFC: Agent identity management — ghost identities, service accounts, credential bootstrap, sanitization #136
beorngb
started this conversation in
RFC / Design
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Context
Working through an MCP-tool-access regression on a service-identity agent surfaced several overlapping issues in how GridBear models, stores, and routes agent identities. They are individually fixable but together point to a coherent gap in the design. This RFC documents the problems end-to-end and proposes a path forward.
Problems observed
1. "Ghost identities" in the data model
Some agents operate as their own non-human identity (service account). Today the canonical user table is
app.users, but:app.users, yet appear inapp.user_service_accounts(unified_id=..., service_type=..., account_id=...)and have credentials in the vault. They work at runtime becauseunified_idis a free-text column, not a foreign key toapp.users.app.usersbut have nouser_service_accountsrow and no vault credentials — the admin UI shows permissions configured but the actual connection isn't provisioned.Consequences:
/usersand/permissionsshow onlyapp.usersrows, so ghost identities are invisible there — no way to audit "who can speak as what"service_accountoverrides in agentcontext_optionscan point at either kind of identity and nothing validates the target has credentials2. No admin UI for
service_accountcontext_options.service_accountis currently editable only via direct SQL / ORM. The admin agent editor doesn't expose it. Users discover it exists through reading source or memory docs.3. No admin UI for ghost-identity credential bootstrap
OAuth flows in
/me/connectionsare tied to the logged-in portal user. A service identity (an email automation, a workflow persona) has no portal password and can't go through the flow. The credentials are populated out-of-band — script, SQL, vault manipulation — none of it documented or reproducible by clicking.4. Sanitization asymmetry in MCP server names
Multi-instance MCP providers (e.g. per-account Gmail) expose servers as
<provider>-<account>. MCP tool names must match^[a-zA-Z0-9_-]{1,64}$, so the gateway emits sanitized names (.and@replaced with_). The_sanitized_to_originalreverse map keeps the two in sync;filter_tools_by_permissionsdoes the reverse lookup correctly.In practice this asymmetry is confusing for operators — what the admin UI shows, what the agent config stores, and what appears in tool names are three different strings. Bug-prone without tooling.
5. Stale tool cache masking identity changes
Separately tracked and fixed in #135:
_tools_cache(120 s TTL) wasn't invalidated on agent reload orservice_accountswap, so identity changes silently didn't take effect until restart. This was the proximate cause of the debugging session that triggered this RFC — but the underlying complexity of "multiple identities one agent routes through" made it unusually hard to diagnose.6. Admin UI gives no feedback on broken identity chains
When
service_account=Xis set on an agent butXhas nouser_service_accountsrows (or no vault credentials) for the MCP servers listed inmcp_permissions, the admin UI happily shows "OK". Only at runtime does the agent report "no tools available" — and the failure mode is indistinguishable from a real MCP connectivity problem.Proposed direction
Treating the above as a single coherent feature rather than seven separate patches.
A. Introduce "Service Identities" as a first-class concept
A service identity is a non-human principal an agent can act as. Two approaches possible:
app.userswith anis_service_accountboolean (nullable password already supports bot-only users per the user-unification work). Admin UI gains a separate tab / filter for service accounts vs human users.app.service_identitiesparallel toapp.users, with its own lifecycle. Cleaner separation but duplicates a lot of the user model (display_name, email, relations).Suggested: option 1 (flag on
app.users). Minimises schema churn, lets the same auth infra serve both when needed.B. Admin page for service identities
Under
/usersor a new/service-identities:name,display_name, optionalemail, no password neededunified_iduser_service_accounts+ vault entries for the identity, with "remove" actionsC. Make
service_accounteditable in the agent editorIn the core agent editor page, add a
service_accountdropdown populated from identities (human + service) that have at least oneuser_service_accountsrow. Null = use sender's identity.D. Validation and preview in the admin UI
When an agent is saved, validate the identity chain: for each server in
mcp_permissions, check that the effective identity (service_accountif set, otherwise the invoking user) has the requireduser_service_accountsrows / vault credentials. Surface warnings inline: "service_account=X→server-AOK ·server-Bmissing credentials".E. Normalize the server-name sanitization
Pick one canonical representation and use it everywhere the admin UI touches. Suggestion: keep originals in
user_service_accounts.account_id(needed for display), but store sanitized names inagent_configs.mcp_permissionsanduser_mcp_permissions.server_name. One-shot migration. This eliminates the "which form do I type in the UI" ambiguity and the fragility of the reverse lookup.F. Promote "shared mailbox" identities to the reference pattern
When a mailbox is shared (team inbox,
hello@,info@, …), the natural owner identity is a service identity named after the mailbox or the team, not the first human who happened to OAuth it. Coupling unrelated agents to a human's identity for credential reuse is an accidental side effect we should steer operators away from. Having a canonical shared-mailbox identity avoids that accidental coupling.Migration story
None of the above requires breaking changes. Existing ghost identities keep working. A one-shot migration can:
app.usersrows for anyunified_idinuser_service_accounts/ vault that doesn't have one, withis_service_account=trueand no passwordmcp_permissions/user_mcp_permissions.server_nameto sanitized formScope of this RFC
This is the design discussion, not an implementation plan. Once the shape of A–F is agreed, individual sub-pieces can be scoped into separate plans and PRs. Probable split:
app.users.is_service_accountflag + small admin UI tweaks to show/filterservice_accountdropdown in agent editor + validation warningsRelated
_tools_cacheinvalidation (tactical fix for the symptom that triggered this discussion)All reactions