Let an admin mint an API key on behalf of a user - #2902
Merged
Conversation
An orchestrator dispatching work for a team had one shared key, so every
agent it launched authenticated as that key's owner. Minting a key per
person lets it act as each of them instead, which is what makes their own
Claude subscription apply through the ordinary path.
POST /admin/users/{id}/api-keys, admin-only, idempotent per (user, name)
so minting on every login does not pile up credentials. Refuses to mint
for a subject with no Helix user: signing up to the orchestrator must not
be enough to get an identity here once it is web-facing.
Spec-Ref: helix-specs@6789a48:002391_fix-these-critical-bugs
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.
Pairs with helixml/helixos#90.
An orchestrator that runs agents for a team currently holds one shared API key, so
Helix records every agent's work against that one account and resolves its Claude
subscription. One person's expired token breaks the whole fleet, and nobody can
run their own agent on their own subscription.
#2899 and #2900 addressed that by routing credentials —
CredentialOwnerIDplus a delegation grant. That works, but the grant is org-shaped: it says "this
org's automation may spend my quota", which is broader than "my own agents may".
Holding a key per person removes the need for any of that. Sessions and triggers
the orchestrator creates are genuinely owned by the person, so their subscription
applies through Helix's ordinary resolution — no delegation, no new concepts.
The endpoint
POST /api/v1/admin/users/{id}/api-keys?name=…, admin-only, returns the key.reconcile pass; a fresh key each time would leave a pile of live credentials.
keeps it safe once the orchestrator is web-facing: signing up there must not
be enough to get an identity minted here. Both sides share an IdP, so someone
who has logged into Helix already has a row; someone who hasn't is not a person
we will act as.
Nothing else changes. This is additive — no existing behaviour is touched.
Why this is safe to add
The privilege already exists: an admin key can already do anything, including
minting keys. What changes is that the orchestrator's sessions stop all being
attributed to one service account, so "who ran this" becomes answerable — an
audit improvement, not just a credentials one.
Worth stating plainly, though: this lets an admin act as any user without their
involvement, which is exactly the property #2899's consent gate was designed to
avoid. That gate remains the right mechanism for member-to-member delegation.
This is for the narrower case of a trusted first-party orchestrator, where the
alternative is the whole fleet sharing one account.
Testing
go build ./pkg/server/clean,go test -run TestAdmin ./pkg/server/passes.Five new cases: the key is owned by the target user and not the caller;
re-minting the same name returns the existing key without creating another;
non-admins get 403; an unknown user gets 404 rather than a minted identity; and
name is required.
go vetreports two pre-existing issues inmcp_backend_external.go, untouchedhere.
NOT tested against a running stack — no Helix dev stack in this environment.
The handler logic is unit-tested; the round trip from the orchestrator is not.
Deploy note
The HelixOS service account on meta.helix.ml is not currently an admin —
verified:
GET /api/v1/usersreturns 401 with its key. Until that's granted, thepaired PR mints nothing and every bot stays on the shared key, so both sides are
inert rather than broken.