Problem Statement
OpenShell can dynamically obtain provider tokens for sandbox traffic, but the existing client_credentials flow represents the sandbox/supervisor client rather than the end user. That works for service-to-
service access, but it does not preserve user delegation semantics for APIs that need to authorize based on both:
- the human user who initiated or owns the sandbox activity
- the sandbox agent identity that is acting on that user's behalf
For OIDC/OAuth providers that support token exchange, the desired final access token should keep the user as the token subject while identifying the sandbox agent as the authorized party (azp) or equivalent
client identity. This lets downstream services apply user-level authorization, auditing, and policy decisions without losing the fact that the request came from an OpenShell sandbox agent.
The value is strongest for enterprise APIs where access should be delegated, attributable, and scoped: the target service can answer "which user is this for?" and "which sandbox/agent is acting?" from a single
presented token.
Proposed Design
Extend dynamic provider token grants so a provider profile can request OAuth 2.0 token exchange (urn:ietf:params:oauth:grant-type:token-exchange) as an alternative to the existing client_credentials grant.
At a high level, the flow should be:
- A user authenticates to the OpenShell gateway using OIDC.
- The CLI can create or update a provider credential from the current OIDC token, for example via
provider create/update --from-oidc-token.
- A provider profile declares a dynamic credential with
token_grant.grant_type: token_exchange.
- The profile also declares the stored user token as the
subject_token for the exchange.
- When sandbox traffic matches a profile endpoint requiring the dynamic credential, the supervisor requests an intermediate subject token from the gateway.
- The supervisor presents its SPIFFE JWT-SVID to the gateway with that request.
- The gateway validates the supervisor SVID and uses the SVID subject as the requested audience for the intermediate token, preventing the supervisor from forging arbitrary audiences.
- The gateway performs an intermediate token exchange using its own SPIFFE JWT-SVID as the client assertion and the stored user token as the subject token.
- The gateway returns the intermediate token to the supervisor.
- The supervisor performs the final token exchange at the same configured token endpoint, using:
- its own SPIFFE JWT-SVID as the client assertion
- the intermediate token as the subject token
- the configured final audience/scope for the target API
- The supervisor injects the resulting access token into the outbound HTTP request according to the profile credential placement rules.
The intended final token semantics are:
sub or equivalent subject claim identifies the user
azp or equivalent authorized-party/client claim identifies the sandbox/supervisor SPIFFE identity
- the token audience and scopes are suitable for the target API
- the target API receives only the final exchanged token, not the original user token or intermediate token
Security constraints:
- The gateway must not allow the supervisor to choose an arbitrary intermediate-token audience.
- The intermediate audience should be derived from the validated supervisor SVID subject.
- The gateway should validate the supervisor SVID against the SPIFFE OIDC/JWKS discovery material.
- The gateway should use its own SPIFFE JWT-SVID as the client assertion for the intermediate exchange.
- Stored user subject tokens should be treated as provider credentials and should expire according to their token expiry.
- Updating a provider from the current OIDC token should reuse the existing provider update path where possible.
Profile shape:
credentials:
- name: subject_token
required: true
- name: access_token
auth_style: bearer
token_grant:
grant_type: token_exchange
token_endpoint: https://idp.example.com/realms/example/protocol/openid-connect/token
audience: target-api
jwt_svid_audience: https://idp.example.com/realms/example
client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer
requested_token_type: urn:ietf:params:oauth:token-type:access_token
subject_token:
source: provider_credential
credential: subject_token
subject_token_type: urn:ietf:params:oauth:token-type:access_token
CLI behavior:
- provider create --from-oidc-token should create a provider credential containing the current user access token when the profile declares a token-exchange subject token.
- provider update --from-oidc-token should refresh or replace the stored subject-token credential.
- If a profile has exactly one token-exchange subject credential, the CLI can infer the destination credential.
- If multiple subject-token credentials are declared, the user should specify the destination credential explicitly.
### Alternatives Considered
### Continue using client_credentials
This is simpler and already supported, but the resulting token represents the sandbox/supervisor client rather than the user. It is not sufficient for user-level delegated authorization or audit trails.
### Give the sandbox the user's raw OIDC token
This would preserve the user subject, but it exposes the user's token directly to sandbox code and does not bind use of the token to the sandbox agent identity. It also makes it harder to scope the final
audience and lifetime for the target API.
### Let the supervisor request any intermediate audience
This would be flexible, but unsafe. The gateway should derive the intermediate audience from the verified supervisor SVID subject so the supervisor cannot mint an intermediate token for another client
identity.
### Put all audience configuration in the profile
The final target audience belongs in the profile, but the intermediate audience may only be known after sandbox creation because it depends on the supervisor SPIFFE identity. The gateway should derive that
intermediate audience at request time.
### Agent Investigation
The current dynamic token grant path is profile-driven and performed by the supervisor so it can use the sandbox/supervisor SPIFFE identity for client assertions. That makes token exchange a natural extension
of the existing token grant behavior.
The implementation requires work across:
- provider profile schema and validation for grant_type: token_exchange
- provider create/update CLI support for storing a user OIDC token as a provider credential
- gateway RPC support for supervisor-requested intermediate subject-token exchange
- SPIFFE JWT-SVID parsing and validation in shared/server code
- supervisor token grant logic to perform the two-stage exchange
- cache keying so exchanged tokens are scoped by provider, endpoint, audience, grant type, subject-token credential, and provider revision
- docs and examples showing the user-subject / sandbox-agent-authorized-party flow
### Checklist
- [x] I've reviewed existing issues and the architecture docs
- [x] This is a design proposal, not a "please build this" request
Problem Statement
OpenShell can dynamically obtain provider tokens for sandbox traffic, but the existing
client_credentialsflow represents the sandbox/supervisor client rather than the end user. That works for service-to-service access, but it does not preserve user delegation semantics for APIs that need to authorize based on both:
For OIDC/OAuth providers that support token exchange, the desired final access token should keep the user as the token subject while identifying the sandbox agent as the authorized party (
azp) or equivalentclient identity. This lets downstream services apply user-level authorization, auditing, and policy decisions without losing the fact that the request came from an OpenShell sandbox agent.
The value is strongest for enterprise APIs where access should be delegated, attributable, and scoped: the target service can answer "which user is this for?" and "which sandbox/agent is acting?" from a single
presented token.
Proposed Design
Extend dynamic provider token grants so a provider profile can request OAuth 2.0 token exchange (
urn:ietf:params:oauth:grant-type:token-exchange) as an alternative to the existingclient_credentialsgrant.At a high level, the flow should be:
provider create/update --from-oidc-token.token_grant.grant_type: token_exchange.subject_tokenfor the exchange.The intended final token semantics are:
subor equivalent subject claim identifies the userazpor equivalent authorized-party/client claim identifies the sandbox/supervisor SPIFFE identitySecurity constraints:
Profile shape: