Relationships
#4692 serve: let methods act on swamp-club as the user who started the run (on-behalf-of token exchange)
Opened by swamp_lord · 11/12/2026
Problem
A method running on swamp serve can't act on another service as the person who started the run. MethodContext (src/domain/models/model.ts) carries no caller identity, and serve holds no credential it could use for that person. The run's initiatedBy is recorded for audit, but extension code can't read it.
So any extension that calls the swamp-club API from serve (for example @swamp/issue-lifecycle moving Lab issue status and posting lifecycle entries) has to use one service key held by the serve. Every action then shows as that service account, not the person. The key also has to be an admin key, because platform and security issues are visible only to admins and authors, so anyone allowed to run the method effectively gets admin on the Lab. The workaround is to run such models locally against the user's own auth.json, which gives up the shared serve state.
Passing the user's key as a method input isn't acceptable: it's a secret and ends up in run history.
Proposal: on-behalf-of tokens for method runs (OAuth 2.0 Token Exchange, RFC 8693)
- When a person starts a run on an OAuth-mode serve, serve uses its own client credentials (the per-serve swamp-club OAuth client from serve links) and the caller's identity to request a token from swamp-club, roughly "serve X acting for user Y".
- swamp-club issues a short-lived, down-scoped token:
- audience: the swamp-club API
- scopes requested per method or declared by the extension, e.g.
lab:write - TTL bounded by the run
- an
actclaim naming the serve It can never exceed the user's own permissions.
- Methods get it through a caller API, e.g.
context.caller={ principalId, username, initiatedBy }andcontext.caller.swampClubToken({ scopes }). The token is never persisted to data, outputs, inputs or logs, and is redacted like vault values. - swamp-club records both subject and actor on every write, e.g. "john (via ops.example.com)", and audits the exchange.
- Runs with no person behind them (
service:scheduler,service:webhook, worker tokens) get no on-behalf-of token; the method fails closed unless it declares a service identity.
Why exchange rather than pass-through
Forwarding the user's real swamp-club token would hand a long-lived, full-scope credential to every extension on the serve, and serve doesn't hold that token anyway: the OAuth login creates a serve session. An exchanged token is narrow, short-lived, revocable, and traceable to one run.
Acceptance
- swamp-club exposes a token-exchange grant for registered serve clients, with scope and TTL limits, the
actclaim and audit. - serve mints the token lazily on first use in a user-initiated run and attaches it to the method execution context; it is never written anywhere.
-
MethodContextexposes the caller identity and a token accessor; extensions can declare the scopes they need. -
@swamp/issue-lifecycleuses it when running on serve, so Lab entries and status changes are attributed to the person. - Docs: serve guide section on acting on behalf of the caller.
Open
No activity in this phase yet.
Sign in to post a ripple.