Skip to content

feat(providers): add gateway-owned interactive OAuth login framework #3331

Open

Description

@johntmyers

User Story

As an OpenShell user, I want to authorize a subscription-backed upstream provider through the OpenShell CLI, so that the gateway can own and refresh a dedicated OAuth grant without importing credentials from another host application or exposing reusable tokens to a sandbox.

Problem Statement

OpenShell has gateway-managed refresh for provider credentials, credential-driver-backed secret storage, provider attachment, native endpoint policy, and egress credential injection. It does not have a common way to bootstrap an interactive OAuth grant for a provider.

Current and proposed subscription-provider integrations therefore implement or propose provider-specific setup outside the shared provider lifecycle:

Importing an existing application's OAuth state gives two clients ownership of one rotating refresh-token lineage. It also couples provider setup to host-specific files, credential formats, and installed applications. Implementing separate CLI flags and lifecycle code for every provider would duplicate browser/device interaction, persistence, cancellation, status, refresh installation, and failure handling.

OpenShell needs one gateway-owned interactive login lifecycle with small, trusted provider-specific adapters.

Impact / Why This Matters

Without this capability, users must choose between signing in inside a sandbox, copying reusable credentials, harvesting another application's host login, or operating a separate host-side inference gateway. These workarounds weaken the sandbox credential boundary, create refresh races, depend on unstable private file formats, and make remote gateways difficult to operate.

A common login framework would make subscription providers behave like normal OpenShell providers: authorize once through the CLI, attach the provider to a sandbox, and let the gateway persist and refresh the grant through its configured credential driver. It would also prevent Codex, Grok, Claude, and future providers from each creating incompatible setup commands and token-storage paths.

Proposed Design

User experience

A login-capable provider can be created without importing or supplying a reusable OAuth credential:

openshell provider create --name codex --type codex-subscription
openshell provider login codex

provider login starts a new authorization grant dedicated to that OpenShell provider instance. It must not read or reuse Codex, Claude Code, Grok, or other host application credential stores.

The provider, login session, resulting grant, refresh state, and credential handles belong to exactly one workspace. The CLI uses its selected workspace, and every login, status, completion, cancellation, reauthorization, logout, attachment, and credential-resolution operation must enforce that workspace scope. Providers with the same name in different workspaces remain independent.

For a device-authorization provider, the CLI prints the verification URL and user code, optionally opens the URL, reports expiry, waits for completion, and supports cancellation. For an authorization-code provider, the CLI opens the authorization URL and completes a PKCE-protected browser flow. The interaction must work when the CLI and gateway are on different machines.

The same command reauthorizes a provider in reauthorization_required state. Users can inspect bounded, non-secret status and log out:

openshell provider login status codex
openshell provider logout codex

Exact command nesting may follow existing CLI conventions, but login, status, cancellation, reauthorization, and logout must be provider-neutral rather than separate --login-codex, --login-xai, or --login-claude flows.

After login, attaching the provider authorizes its declared native inference endpoints and egress credential injection. The workload calls the provider's native endpoint; this design must not introduce or depend on inference.local.

End-to-end interaction

sequenceDiagram
    autonumber
    actor U as User
    participant C as OpenShell CLI
    participant G as Gateway
    participant I as Provider OAuth service
    participant D as Credential driver
    participant M as Gateway metadata store
    participant S as Sandbox
    participant E as Egress enforcement
    participant A as Provider inference API

    U->>C: Run provider login for a named provider
    C->>G: Start login in the selected workspace
    G->>G: Bind an expiring session to workspace provider and version

    alt Device authorization
        G->>I: Request device authorization
        I-->>G: Return verification URL user code and polling interval
        G-->>C: Return device interaction
        C-->>U: Display URL and user code
        U->>I: Approve the dedicated OpenShell grant
        G->>I: Poll for completion
    else Browser authorization with PKCE
        G-->>C: Return authorization URL and login ID
        C-->>U: Open browser
        U->>I: Approve the dedicated OpenShell grant
        C->>G: Relay callback result for the login ID
        G->>I: Exchange code using gateway held PKCE verifier
    end

    I-->>G: Return access token refresh token expiry and account metadata
    G->>D: Stage access token and gateway only refresh material
    D-->>G: Return opaque credential handles
    G->>M: Atomically commit workspace scoped handles and refresh state
    G-->>C: Mark provider ready
    C-->>U: Report login succeeded

    opt Refresh before credential expiry
        G->>D: Resolve refresh material by handle
        D-->>G: Return refresh material
        G->>I: Refresh the grant
        I-->>G: Return rotated tokens and expiry
        G->>D: Stage replacement credential values
        D-->>G: Return replacement handles
        G->>M: Version matched commit of handles and expiry
        G->>D: Delete superseded handles after commit
    end

    S->>E: Request a native provider endpoint
    E->>G: Resolve attached provider for sandbox and workspace
    G->>M: Verify workspace scoped provider attachment and state
    G->>D: Resolve current access credential
    D-->>G: Return current credential
    G-->>E: Return authorized policy and credential
    E->>A: Inject credential and forward request
    A-->>S: Return inference response
Loading

Shared behavior

The common framework owns:

  • authorization-session creation, expiry, polling or callback completion, cancellation, and cleanup;
  • PKCE and OAuth state generation and validation;
  • generic CLI rendering for device-code and browser interactions;
  • binding a login session to the authenticated caller, exact workspace, provider ID, provider type, and provider resource version, with no cross-workspace lookup or fallback;
  • atomic installation of the initial access token, refresh material, expiry, account metadata, and refresh state;
  • storage through the gateway's configured credential driver, including the default database credstore and configured Vault, Kubernetes Secrets, or external drivers;
  • refresh, rotated refresh-token persistence, retry classification, and reauthorization_required transitions using the existing gateway refresh machinery;
  • re-login, logout, remote revocation where supported, local deletion, and cleanup of superseded credential handles;
  • redacted status and audit events that never include codes, tokens, callback parameters, or credential-bearing URLs;
  • race handling for concurrent login, refresh, provider update, provider deletion, and logout operations;
  • fail-closed behavior when credentials expire, the credential driver is unavailable, or refresh requires user action.

Provider metadata stores only non-secret state and opaque credential handles. Access tokens, refresh tokens, and other reusable authorization material are stored through the configured credential driver. Neither login nor refresh returns reusable credentials to the CLI or sandbox.

Refresh scheduling and credential-driver storage remain workspace-scoped through the provider identity. A login or refresh in one workspace must not read, replace, revoke, publish, or delete the grant or credential handles of a provider in another workspace.

Provider-specific behavior

Each supported provider supplies a trusted adapter for its documented or explicitly supported authorization contract. An adapter may define:

  • supported interaction type: device authorization, authorization code with PKCE, or both;
  • authorization, device-code, token, account, and revocation endpoints;
  • OAuth client identity, scopes, audience, and required provider parameters;
  • token and error response parsing, including retryable versus terminal failures;
  • account, organization, workspace, or subscription binding that must be preserved with the grant;
  • provider-specific logout/revocation behavior;
  • required inference origins, paths, headers, and request transformations.

The CLI should not implement provider token exchange or persistence. It may render bounded provider-supplied guidance, such as an account setting required before device authorization. The gateway remains the sole owner and refresher of the grant.

Interactive login configuration is security-sensitive because it controls where reusable credentials are sent. Initial support should use trusted built-in adapters. Custom profiles must not be able to redirect a built-in adapter's authorization, token, refresh, revocation, or credential-bearing inference endpoints to arbitrary origins.

Initial provider scope

The first implementation should target OpenAI Codex and Claude Code together so the shared framework is proven across both interaction families:

  • Codex is the first device-authorization adapter. It creates a new ChatGPT/Codex grant owned exclusively by the OpenShell provider and does not read or reuse host Codex authentication.
  • Claude Code is the first browser-authorization adapter. It creates a new Claude subscription grant owned exclusively by the OpenShell provider and does not read macOS Keychain entries, ~/.claude/.credentials.json, or any other existing Claude Code login. This depends on Anthropic providing or approving a client contract suitable for an independent OpenShell-owned grant.

The two adapters should share login-session, persistence, status, logout, and refresh plumbing while retaining their provider-specific authorization protocol, account binding, native inference endpoints, headers, and request transformations. Grok remains a follow-on device-authorization adapter under #2742. This issue creates the shared capability; #1925, #2740, #2742, and future provider issues remain responsible for provider-specific compatibility and rollout decisions.

Persistence and refresh lifecycle

A successful login should feed the same provider credential and refresh model already used by gateway-managed refresh rather than establishing a parallel token store.

The initial token installation and subsequent rotations must be version-matched. If storage or metadata commit fails, staged credential handles are removed and the provider's previously committed grant remains authoritative. A late login completion must not resurrect a deleted provider or overwrite a newer login, logout, reconfiguration, or refresh result.

After installation, the existing gateway refresh worker resolves gateway-only refresh material from the configured credential driver, refreshes before expiry, persists a rotated refresh token when returned, publishes the replacement access credential to provider resolution, and marks terminal grant failures as requiring reauthorization. Provider-specific adapters may extend request construction and bounded error classification without duplicating scheduling, storage, concurrency, or status machinery.

Acceptance Criteria

  • openshell provider login <name> starts a provider-neutral interactive authorization flow for a login-capable provider without reading credentials from another host application.
  • The common API represents at least device-code and browser-PKCE interactions, including expiry, completion, cancellation, and sanitized errors.
  • The flow works when the CLI and gateway run on different machines.
  • A successful login creates a distinct grant owned and refreshed exclusively by the gateway.
  • The login session, grant, credential handles, refresh state, status, logout, and provider attachment are bound to one explicit workspace; same-named providers in other workspaces remain isolated.
  • Initial access credentials and gateway-only refresh material are stored through the configured credential driver; provider metadata contains opaque handles and non-secret lifecycle state.
  • Login atomically installs credential handles, expiry, account binding, and refresh configuration, with cleanup after partial storage or a lost version race.
  • The existing gateway refresh lifecycle refreshes the installed grant, persists rotated refresh tokens, publishes updated access credentials, and reports reauthorization_required for terminal failures.
  • Re-login safely replaces a grant without allowing an older login or refresh operation to overwrite the replacement.
  • Logout cancels pending login, prevents concurrent refresh publication, revokes remotely when supported, deletes local credential material, and causes subsequent credentialed requests to fail closed.
  • A sandbox with the provider attached can call only its profile-authorized native endpoints and receives egress credential injection without receiving the reusable token.
  • A sandbox without the provider attached cannot use the grant, and a custom endpoint cannot receive credentials from a built-in login adapter.
  • Login, refresh, logout, diagnostics, logs, and audit events do not expose access tokens, refresh tokens, user/device codes, authorization codes, PKCE verifiers, or credential-bearing URLs.
  • The initial implementation includes a Codex device-authorization adapter and a Claude Code browser-authorization adapter, each creating a distinct gateway-owned grant without importing host application credentials.
  • Codex and Claude Code have provider-specific protocol tests plus gateway/credential-driver integration coverage demonstrating that both adapters use the same shared login and persistence lifecycle.
  • Shared contract tests cover device approval, denial, expiration, cancellation, polling backoff, transient token errors, terminal grant rejection, rotated refresh tokens, driver failure, provider deletion, reconfiguration, and concurrent re-login/refresh/logout.
  • Gator can use a persistent gateway-owned Codex provider without reading ~/.codex/auth.json, exporting host Codex tokens, or resetting refresh state from host credentials.
  • Published provider documentation explains login, attachment, status, reauthorization, logout, native endpoint behavior, remote-gateway interaction, and credential-driver persistence.

Alternatives Considered

Import an existing host application login

Rejected as the normal workflow. It gives OpenShell and the host application competing ownership of a rotating refresh-token lineage, depends on private host storage formats, and makes remote gateways awkward. A dedicated OpenShell grant has one owner and one refresh lifecycle.

Authenticate inside the sandbox

Rejected. It places reusable subscription credentials inside the less-trusted workload and bypasses the provider credential boundary.

Implement independent CLI login flags for every provider

Rejected. It duplicates interaction, storage, refresh installation, cancellation, status, and failure handling. Provider-specific protocol differences belong behind a common provider-login capability.

Continue using a separate host inference gateway

This can remain an optional integration, but it adds another daemon, credential boundary, routing layer, and operational lifecycle. It should not be required to use a subscription-backed provider safely.

Expose authorization entirely through provider profile YAML

Rejected for built-in subscription adapters. Arbitrary authorization and token endpoints would let profile data redirect reusable credentials. Profiles can describe provider capabilities and native egress policy, while trusted code owns credential-bearing protocol contracts.

Agent Investigation

Activity

  1. added
    area:cliCLI-related work
    area:inferenceInference routing and configuration work
    area:gatewayGateway server and control-plane work
    state:acceptedA maintainer decided OpenShell should pursue this issue
    on Sep 14, 2026
  2. johntmyers commented on Sep 14, 2026

    @johntmyers
    CollaboratorAuthor

    🏗️ build-plan

    Implementation Plan

    Issue type: feat
    Complexity: High
    Confidence: Medium — the shared framework is grounded in current code. Each production adapter requires evidence of a supported independent OpenShell client contract; Claude Code remains dependent on Anthropic approval.

    Summary

    Add a provider-neutral, gateway-owned interactive OAuth lifecycle covering device authorization and browser PKCE. The CLI renders user interaction and relays callback results, while the gateway owns provider HTTP exchanges, session state, PKCE/device secrets, credential-driver persistence, refresh, reauthorization, and logout.

    Preserve existing API-key and host-discovery profiles. Add dedicated trusted subscription profiles and bind each login adapter to an exact built-in profile ID. Sandbox traffic continues through native provider endpoints and existing endpoint-bound credential injection; do not introduce inference.local or a sandbox credential store.

    Scope

    • Public API: proto/openshell.proto, generated Python bindings under python/openshell/_proto/, committed Go bindings under sdk/go/proto/openshellv1/, plus Rust and SDK mocks affected by the expanded OpenShell service. TypeScript files under sdk/typescript/src/gen/ remain generated build output.
    • Gateway login framework: add crates/openshell-server/src/provider_login/ and crates/openshell-server/src/grpc/provider_login.rs; wire them through crates/openshell-server/src/grpc/mod.rs and crates/openshell-server/src/lib.rs.
    • Durable state: add an internal proto such as crates/openshell-server/proto/provider_login.proto in a new storage package and update crates/openshell-server/build.rs. Do not modify the frozen openshell.storage.v1 schema in place.
    • Atomic persistence: update crates/openshell-server/src/persistence/mod.rs, sqlite.rs, postgres.rs, and persistence tests.
    • Credential storage and refresh: update crates/openshell-server/src/credentials.rs, provider_refresh.rs, and relevant lifecycle code in grpc/provider.rs.
    • Supervisor enforcement: update provider-environment publication, crates/openshell-core/src/provider_credentials.rs, the settings loop in crates/openshell-sandbox/src/lib.rs, and HTTP/WebSocket injection paths as required for authorization epochs, freshness deadlines, and logout acknowledgments.
    • Trusted provider adapters: after the respective vendor-contract gates, add Codex and Claude adapters below crates/openshell-server/src/provider_login/. Select adapters using exact trusted built-in profile IDs, never caller-controlled endpoint data.
    • Provider profiles: add providers/codex-subscription.yaml and providers/claude-code-subscription.yaml, registered and validated through crates/openshell-providers/src/profiles.rs. Keep providers/codex.yaml and providers/claude-code.yaml backward compatible.
    • CLI: update crates/openshell-cli/src/main.rs and crates/openshell-cli/src/commands/provider.rs; add provider-neutral callback/rendering support. Reuse only the loopback/browser primitives from oidc_auth.rs; provider token exchange and persistence remain in the gateway.
    • CLI/SDK tests: update crates/openshell-cli/tests/provider_commands_integration.rs and service mocks such as crates/openshell-sdk/tests/client_mock.rs.
    • Gator: update scripts/agents/run.sh, scripts/agents/gator/agent.yaml, scripts/agents/gator/providers/codex-gator.yaml, scripts/agents/runtime/harnesses/codex/exec.sh, and related READMEs.
    • Documentation: update architecture/gateway.md, docs/sandboxes/manage-providers.mdx, docs/providers/profiles.mdx, and docs/get-started/quickstart.mdx; add provider-specific pages and docs/index.yml entries only if needed.

    Implementation Steps

    1. Define the provider-neutral RPC contract and CLI shape.

      • Add start, poll, complete, status, cancel, and logout RPCs for provider login.
      • Represent device-code and browser-PKCE interactions with explicit enums/oneofs. Separate login-session phases (pending, exchanging, recovery-required, succeeded, denied, expired, canceled, failed) from active provider readiness (unauthorized, ready, reauthorization-required, logged-out). A pending or canceled replacement session must not imply that the existing grant is unusable.
      • Require an explicit WorkspaceSelector on every RPC. Bind each opaque login ID to the exact provider ID, stable authenticated principal identity, workspace, trusted adapter, configuration generation, and replacement-session generation. Keep ordinary active-grant refresh versions separate so refresh cannot invalidate a pending replacement login.
      • Require workspace Admin for start, device polling that drives authorization, completion, cancellation, and logout; workspace User may read bounded provider readiness. Session interaction retrieval, device polling, and completion additionally require the initiating principal. Another workspace Admin may cancel a session or log out the provider, but cannot retrieve its interaction secrets or complete it. Recheck current role, membership, and active workspace state at each operation and immediately before grant publication; session possession is not authorization. Background refresh follows workspace/provider lifecycle state, not the lifetime of the initiating CLI connection.
      • Mark authorization codes, OAuth state, device codes, callback payloads, and similar values with protobuf secret annotations. Return only the interaction data needed by the user and sanitized status afterward.
      • Implement provider-neutral commands centered on provider login <name> and provider logout <name>, with status and cancellation following existing Clap conventions.
    2. Add a durable, replica-safe login-session state machine.

      • Persist non-secret state: workspace, provider identity and version, adapter ID, caller subject, interaction type, expiry, polling deadline, terminal status, authorization epoch, and resource version.
      • Store device codes, PKCE verifiers, OAuth state, and other sensitive session material through CredentialRuntime, scoped by workspace/provider/session object ID.
      • Permit one active session per workspace/provider using database uniqueness and CAS. In-memory locking may reduce duplicate work but is not a correctness boundary.
      • Make start, poll, complete, cancel, expiration cleanup, provider deletion, and logout idempotent with caller/workspace-scoped operation IDs. Resolve a lost success response to the existing result instead of creating a second grant. Reject late completion after relevant provider reconfiguration/deletion, newer replacement login, logout, revoked caller authorization, or workspace mismatch. Ordinary refresh of the active grant does not invalidate the replacement session.
      • Claim durable operation ownership before upstream device polling, code exchange, refresh, or revocation. Persist attempt identity, owner, deadline, and outcome; CAS after an HTTP request alone is insufficient. Serialize refresh per grant lineage and device polling/code exchange per session across replicas. Lease expiry must not cause an overlapping retry of an upstream operation whose outcome is unknown. Fencing prevents stale local publication but cannot fence the vendor OAuth server: reconcile durable results or use explicitly supported vendor recovery, otherwise surface recovery-required/reauthorization.
      • Apply bounded session and operation quotas per caller/workspace and gateway, including abandoned sessions. Persist expiry/cleanup work independently of client polling. Use gateway/database time for shared deadlines, account for clock skew, and make cleanup restart-safe.
      • Let bounded client poll calls drive device authorization so any gateway replica can continue the session; enforce provider polling intervals, slow_down, expiration, and gateway rate limits.
    3. Introduce a small trusted adapter interface.

      • Normalize access token, optional refresh token, expiry, bounded account metadata, refresh inputs, and sanitized failure classification.
      • Let adapters own fixed authorization/device/token/revocation origins, client identity, scopes, audience, provider parameters, response parsing, account binding, and provider-specific refresh behavior.
      • Require HTTPS except loopback test fixtures, disable redirects for credential-bearing requests, cap response bodies, set timeouts, and omit provider bodies and credential-bearing URLs from errors and logs.
      • Register adapters only for exact built-in subscription profiles. Reject workspace, platform, or interceptor profiles even if their type resembles a supported adapter.
      • Keep profile YAML incapable of redefining credential-bearing endpoints for built-in interactive adapters. Validate browser authorization/verification URLs against the adapter contract before returning/opening them; bound and sanitize terminal guidance.
      • Validate required expiry, token type, scopes/audience, and account binding against the vendor contract. Verify signed identity claims when used as authority or obtain identity from the trusted account endpoint; do not trust CLI-supplied account metadata. Reject unexpected account/organization changes on refresh. Adapters must declare whether missing refresh tokens are a supported access-only result and how that affects readiness and reauthorization.
    4. Add atomic local grant publication with upstream-outcome recovery.

      • Record operation intent before external effects and stage access/refresh values through the credential driver using unique, recoverable object IDs. Persist only handles and bounded non-secret recovery metadata in SQL. External drivers must support recovering the result of an uncertain store attempt through their contract, or explicitly report that recovery is unavailable.
      • Add a specialized SQLite/PostgreSQL transaction that conditionally publishes provider handles, refresh state, login-session state, authorization epoch, operation outcome, and cleanup records. Match the configuration, active-grant, and replacement-session generations appropriate to the operation. Rework provider_refresh::refresh_provider_credential to remove the split refresh-state/provider publication race.
      • A local database rollback cannot undo upstream refresh-token rotation. Preserve the existing refresh implementation's protection of a staged replacement that may be the only upstream-valid refresh token. If publication definitely did not commit, retain rotated material in durable recovery state and retry only against the same authorized grant generation. If commit outcome is unknown, read/reconcile operation state before deleting any handle. Never return an uncertain grant to normal refresh scheduling with its potentially consumed old refresh token.
      • A crash or lost response after the vendor consumes a code/refresh token may leave no recoverable replacement. Do not blindly replay the old token; use only vendor-supported recovery/idempotency, otherwise require a new login with a bounded error. A recovered stale operation must never resurrect a provider or publish across logout, replacement, deletion, or reconfiguration.
      • Delete staged material only after proving it is unreferenced and no longer required for recovery. After successful publication, enqueue superseded handles for durable cleanup. Track orphan discovery/reconciliation for crashes between external storage and SQL recording; cleanup must protect live and uncertain-commit references. Revoke abandoned grants only when vendor semantics prove this cannot revoke the current grant or another provider's lineage.
      • Keep the existing grant refreshing while replacement login is pending. Maintain separate active-grant and replacement-session generations. At final installation, coordinate with any in-flight refresh, atomically replace the active grant, and advance the authorization epoch. Cancellation/expiry leaves the existing grant and scheduler intact. A genuinely necessary vendor-specific pause requires a documented bounded interruption policy.
      • Reauthorization preserves the verified upstream account/organization by default. Before initiating authorization, show gateway, workspace, provider, and affected sandbox attachments. Require explicit account-change intent for switching accounts, and validate the returned identity before publication. A mismatched grant cannot silently replace a shared provider.
      • Logout first durably disables resolution/publication, advances the authorization epoch, cancels pending login, and records revocation/cleanup work. Driver resolution or remote revocation failure cannot block this local disablement. Retain gateway-only handles only for bounded revocation/cleanup retries, never for serving requests; status distinguishes pending cleanup from enabled credentials. Complete local deletion according to the bounded cleanup policy even if remote revocation remains unavailable.
      • Logout must invalidate credentials already installed in supervisors. Publish the disabled epoch and track acknowledgments from active attachments. Add a bounded authorization-freshness deadline independent of access-token expiry; a disconnected supervisor rejects new credentialed requests when freshness expires. Reject stale environment snapshots after a newer epoch. Define and test a concrete maximum enforcement delay before shipping.
      • Report gateway disablement immediately, but report full enforcement only after active supervisors acknowledge or their freshness deadlines expire. A CLI wait timeout reports enforcement-pending, not complete revocation. The contract blocks new credentialed requests, including new operations on persistent WebSockets: close affected authenticated WebSockets on invalidation/deadline. Already-dispatched HTTP requests/streams may finish; disclose this boundary and do not claim upstream work has been canceled. A supervisor without the required enforcement capability cannot consume these new interactive grants.
    5. Implement provider-neutral CLI interaction.

      • For browser PKCE, bind a loopback callback before starting login and bind its exact validated redirect URI to the session and code exchange. Require S256 and cryptographically random state/verifier. Enforce the approved loopback host, port, and callback path; reject wildcard/non-loopback redirect destinations. Bound parsing, reject duplicate security parameters, and reject unsolicited/invalid-state callbacks without consuming the legitimate session. Return a static no-store callback page without reflected parameters or third-party resources. Relay code/state without logging them.
      • For device authorization, print verification URI and user code in the intended interactive output, optionally open the URI, show expiry, and poll through the gateway using server-supplied timing. Interaction responses are the explicit exception to redaction; ordinary status, logs, telemetry, audit events, and diagnostics must never contain codes or credential-bearing URLs. Support no-browser operation and document whether any machine-readable interaction output contains sensitive one-time values.
      • On Ctrl-C, attempt cancellation; report cancellation-pending if unreachable and rely on authoritative server expiry. Re-running login resumes the initiating caller's pending session where possible instead of silently replacing it. If a browser callback listener cannot be safely restored, explicitly cancel/restart that attempt. Another caller receives bounded conflict information and can use authorized cancellation.
      • Distinguish a remote gateway with a local CLI/browser from a headless or SSH-hosted CLI. Document the supported device flow or approved loopback forwarding procedure for headless operation; never imply a remote browser can reach the CLI loopback automatically. Provide actionable guidance when device login is disabled by account/workspace policy.
      • Display verified upstream account/organization, provider readiness, expiry, pending replacement state, and logout enforcement/cleanup status without exposing reusable credentials. Define stable exit codes and sanitized machine-readable status for Gator and scripts.
      • Map sanitized gateway statuses to actionable errors for denial, expiry, driver outage, stale provider version, unsupported provider, and reauthorization. Return a clear unsupported-feature error from older gateways.
    6. Implement the Codex device-authorization adapter and subscription profile.

      • Before landing production support, record evidence of a supported independent OpenShell client identity or registration mechanism, device/token endpoints, scopes/audience, account restrictions, expiry/rotation behavior, revocation scope, and native API compatibility. Documentation of codex login --device-auth alone does not establish that independent client contract. Confirm a new login creates a separate grant lineage.
      • Use that supported Codex device contract and create a distinct OpenShell-owned grant; never read ~/.codex/auth.json. If the contract is unavailable, leave production Codex support gated while exercising the framework with test adapters.
      • Inject only the short-lived access credential and required non-secret account binding. Store the refresh token solely as gateway refresh material.
      • Declare only verified native inference origins/paths and bearer/account behavior. Authorization hosts belong to the gateway adapter, not sandbox egress policy.
      • Feed refresh through the existing scheduler/status model, extending request construction only where the trusted Codex contract differs from generic OAuth.
    7. Gate Claude Code on an approved vendor contract.

      • Before landing the adapter, document an Anthropic-approved public client identity or registration mechanism, loopback redirect rules, endpoints, scopes/audience, token/header shape, rotation semantics, account binding, and native inference behavior.
      • Then implement the browser-PKCE adapter and dedicated subscription profile against that contract.
      • Do not reuse Claude Code's private client ID, harvest Keychain or ~/.claude/.credentials.json, embed a confidential secret, or guess undocumented endpoints.
      • Keep the shared framework and Codex deliverable independently mergeable if Anthropic does not yet authorize third-party subscription grants; leave the Claude acceptance item explicitly blocked rather than shipping a private-contract imitation.
    8. Migrate Gator to an already-authorized gateway provider.

      • Add a manifest mode such as gateway_existing that attaches a named provider but neither imports host credentials nor creates/reconfigures its refresh grant.
      • Make Gator require a persistent Codex subscription provider in the selected workspace; remove host file_json sources, embedded client identity, and host-sourced refresh configuration.
      • Remove reset-from-host behavior from scripts/agents/run.sh for this provider and report provider login as remediation when the grant is absent or requires reauthorization.
      • If the sandbox harness must materialize Codex-compatible local state, prove it contains no real access or refresh token; this is sandbox compatibility state, not host credential harvesting.
    9. Add observability, documentation, and rollout safeguards.

      • Emit redacted OCSF configuration-state events for completion, reauthorization, cancellation, logout, and terminal failure using only workspace/provider identity and bounded codes.
      • Preserve existing API-key and from_existing flows. Roll out storage/recovery and enforcement capabilities first, shared RPC/session support next, Codex after its contract gate, Gator after authorization, and Claude after its contract gate. Do not enable interactive grants until all serving replicas and consuming supervisors support the required state/enforcement semantics. Document downgrade behavior and reject unsupported mutations instead of silently using legacy refresh.
      • Review related skills with sync-agent-infra, including Gator and provider operation guidance. Update architecture, published docs, and gateway configuration references for any new freshness or cleanup settings.
      • Document trust boundaries, atomicity invariants, remote CLI behavior, workspace scope, persistence, refresh, attachment to native endpoints, and reauthorization/logout.

    Test Plan

    • Unit tests:

      • Session transitions, expiration, single-active-session rules, caller/workspace/provider/version binding, idempotent cancellation, and sanitized status.
      • Device pending/approval/denial/expiration/slow_down, backoff, transient errors, and malformed or oversized responses.
      • Browser redirect validation, state mismatch, PKCE exchange, replayed callbacks, cancellation, and timeout.
      • Exact adapter registry matching, built-in-source enforcement, HTTPS/no-redirect behavior, and rejection of profile endpoint substitution.
      • Credential staging, partial-store and replacement cleanup, missing handles, and driver outages.
      • SQLite/PostgreSQL atomic mutations and deterministic races among completion, refresh, re-login, logout, update, and deletion.
      • Refresh-token rotation and terminal invalid_grant mapping without provider prose or token leakage.
      • CLI parsing/rendering and Ctrl-C cancellation.
    • Integration tests:

      • In-process gRPC tests with injected device and PKCE adapters covering the full CLI-to-gateway lifecycle.
      • Same-name providers in two workspaces, non-member access, cross-workspace login IDs, different caller subjects, and deletion during completion.
      • Database credstore plus mock external-driver success/failure paths proving metadata contains only opaque handles.
      • Attached sandbox succeeds only at declared native endpoints; unattached sandbox and custom endpoints fail closed.
      • Generated-service mocks and protocol tests for Codex and the approved Claude adapter without live-vendor dependencies.
    • E2E tests:

      • Extend Keycloak-backed test infrastructure with test-only adapters for remote browser PKCE/device interaction and gateway refresh; do not add configurable production OAuth endpoints.
      • Extend Kubernetes/Vault credential-driver coverage for login-created access and refresh material.
      • Add Docker/Podman sandbox coverage proving native endpoint injection survives rotation and fails after logout.
      • Add a Gator launcher test that fails if host ~/.codex/auth.json, host token exports, GATOR_CODEX_OAUTH_CLIENT_ID, or reset-from-host logic is consulted.
      • Keep live Codex/Claude authorization as opt-in manual/release smoke tests; CI must not require personal accounts or vendor credentials.
      • Run mise run pre-commit, mise run test, mise run ci before opening a PR, focused provider-login/refresh tests, mise run e2e:provider-refresh-keycloak, and relevant Docker/Podman and Kubernetes credential-driver lanes.

    Required Failure and UX Coverage

    • Crash/restart and lost response after upstream request dispatch, token rotation, external secret storage, SQL publication, and cleanup. Include ambiguous SQL commit and uncertain driver-store results; prove the only usable rotated token is not garbage-collected.
    • Two replicas attempt the same refresh, device poll, or code exchange; owner lease expires while its HTTP request is in flight; a delayed result arrives after logout/replacement. Assert upstream call counts as well as final database state.
    • Lost start/completion/logout responses, resumed CLI, repeated Ctrl-C, abandoned browser, quota exhaustion, clock skew, and cleanup with no connected CLI.
    • Old access token expires while replacement login remains pending; ordinary refresh continues, cancellation preserves readiness, and wrong-account login never silently changes a shared grant.
    • Permission matrix, initiating-principal restrictions, member removal/role downgrade, workspace suspension/deletion, and admin cancellation of another caller's session. Recheck authorization at publication.
    • Logout during driver outage or failed remote revocation; connected/disconnected supervisors, old environment snapshots, freshness expiry, new requests on keep-alive/WebSocket connections, and already-dispatched HTTP streams. Assert the documented enforcement bound and accurate pending/completed CLI status.
    • Headless CLI and remote gateway as separate scenarios; no-browser device flow; invalid callback does not terminate legitimate login; duplicate callback parameters; terminal escape sequences; no code/token leakage through debug logging or machine-readable status.
    • Mixed gateway/supervisor versions and downgrade attempts; new grants cannot bypass enforcement through an old replica or supervisor.

    Delivery and Acceptance

    The shared framework and each approved adapter may land separately. The issue remains incomplete until both production adapter acceptance items are satisfied; mock adapters prove the framework but do not satisfy production Codex/Claude acceptance. Record blocked vendor-contract items explicitly. Gator migration depends on a working authorized Codex provider. Keep the issue's existing acceptance criteria and add the recovery, concurrency, permission, account-binding, and bounded logout contracts above to implementation verification.

    Risks & Open Questions

    • Anthropic contract: Who supplies and approves the OpenShell OAuth client, and does Anthropic allow dynamic loopback ports and independent third-party subscription grants?
    • Atomicity: Neither external credential drivers nor the OAuth server join SQL transactions. Durable intent, recovery of ambiguous outcomes, and protection of rotated material are required in addition to atomic local publication.
    • Multi-replica gateways: Ownership must precede upstream calls. Lease takeover cannot assume the previous request did not execute; local fencing does not provide upstream exactly-once behavior.
    • Endpoint leakage: Bind access credentials only to required native origins, not ancillary telemetry or authorization hosts.
    • OAuth redirects/logging: Disable redirects for credential-bearing HTTP calls and redact URLs/bodies to address SSRF and secret-leak risks.
    • Revocation semantics: Confirm whether each vendor revokes one grant lineage or a broader user session. Define the concrete supervisor freshness/enforcement bound and cleanup retention bound before implementation; remote revocation failure cannot re-enable local use.
    • Codex contract and account binding: Confirm independent client support, minimum verified account/organization metadata, and grant-lineage independence. Unexpected identity changes on refresh fail closed.
    • Compatibility: Dedicated subscription profiles avoid breaking existing API-key/from-existing users; provider-family auto-selection is out of scope.
    • LSM/platform compatibility: No Landlock, SELinux, AppArmor, seccomp, namespace, mount, or compute-driver change is required. Login remains in the CLI/gateway and uses existing supervisor injection. Exercise Linux Docker/Podman and Kubernetes E2E paths; run VM and Windows MXC compile/conformance checks without adding platform-specific code.

    Documentation Impact

    Update architecture/gateway.md, docs/sandboxes/manage-providers.mdx, docs/providers/profiles.mdx, docs/get-started/quickstart.mdx, and Gator documentation. Explain that each grant belongs to one workspace/provider, reusable tokens remain in the configured credential driver, the CLI can target a remote gateway, refresh is gateway-owned, workloads call native endpoints, and reauthorization/logout use the generic lifecycle.

    Document authorization freshness, logout enforcement, recovery, cleanup retention, and account-change behavior. Decide whether freshness/cleanup limits are fixed protocol bounds or configurable gateway settings; do not assume there are no configuration changes. Update docs/reference/gateway-config.mdx for any new fields/defaults and to clarify that existing credential_drivers also store session secrets, grant material, and recovery material. If Anthropic requires per-deployment registration or a confidential client secret, treat that as a separate design decision requiring secure gateway configuration and a full gateway-config documentation update.


    Revision 2 — incorporates security, failure-recovery, supervisor enforcement, authorization, account-binding, UX, and staged-delivery review.

  3. github-actions commented on Oct 16, 2026

    @github-actions

    This issue has had no activity for 14 days and is now marked stale. It may be closed in 14 days if there is no further activity. Comment or remove the state:stale label to keep it open.

  4. klcd commented on Oct 21, 2026

    @klcd

    Will this be implemented or is there a workaround that is being used?

  5. houman44 commented on Oct 21, 2026

    @houman44

    Data point while this is open: a custom provider profile, a copy of claude-code with env_vars: [CLAUDE_CODE_OAUTH_TOKEN], auth_style: bearer, header_name: authorization and endpoints reduced to api.anthropic.com, works with a claude setup-token token on v0.1.5. claude -p ran inside the sandbox, and the sandbox only saw the placeholder. It is undocumented; providers/claude-code.yaml and the docs cover API keys only and tell subscription users to create a Console key.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:cliCLI-related workarea:gatewayGateway server and control-plane workarea:inferenceInference routing and configuration workarea:providersstate:acceptedA maintainer decided OpenShell should pursue this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions