Files
5548405fcb feat(credentials): add provider credential storage drivers (#2437)
* feat(credentials): add provider credential storage drivers

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(credentials): harden credential update handling

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(credentials): harden credential driver security, correctness, and performance

Address review findings from the credential storage drivers PR:

- Route additional_credentials through the driver on refresh to prevent
  silent data loss for multi-credential providers (e.g. AWS STS)
- Clean up stored credential handles on CAS failure during refresh to
  prevent orphaned secrets in external backends
- Enforce namespace validation in the Kubernetes Secrets driver to
  prevent cross-namespace credential access when allow_reference_namespace
  is not enabled
- Cache Vault Kubernetes auth tokens with 80% TTL to avoid re-authenticating
  on every credential operation
- Parallelize resolve_credentials in all three drivers using try_join_all
  for faster sandbox startup
- Add existingSecret support for the KEK Secret to fix helm template/GitOps
  workflows where lookup returns empty and regenerates the key
- Document RBAC blast radius for the Kubernetes Secrets credential driver
  and recommend a dedicated namespace

Signed-off-by: Varsha Prasad <varshaprasad96@gmail.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>

* fix(credentials): add optimistic concurrency, fix thundering herd, parallelize operations

Use resourceVersion optimistic concurrency with retry loop for K8s
Secret ownership checks to prevent TOCTOU races. Switch Vault token
cache from RwLock to Mutex with double-check pattern to prevent
thundering herd on cache miss. Parallelize credential store and delete
operations across independent keys using try_join_all.

Signed-off-by: Varsha Prasad <varshaprasad96@gmail.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>

* fix(credentials): handle partial failures, add delete retry, consolidate cleanup

Replace try_join_all with join_all in credential store/delete operations
to handle partial failures — successfully-stored handles are cleaned up
when another key fails. Add retry loop with conflict detection to
db-credstore delete_credential, matching the K8s driver pattern.
Consolidate 4 manual cleanup_pre_stored_provider_credentials call sites
into a single error handler using an async block. Remove inconsistent
.trim() from db-credstore validate_handle_owner.

Signed-off-by: Varsha Prasad <varshaprasad96@gmail.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>

* fix(credentials): fix retry loop guard and remove unprotected validation

Remove attempt-count guard from 409/Aborted match arms in retry loops
so the post-loop Status::aborted error is reachable after exhausting
retries. Previously, last-attempt conflicts fell through to the
catch-all error arm, producing misleading Status::unavailable errors.

Remove duplicate validation calls that ran after
prepare_provider_credential_update but outside the cleanup-protected
async block, which would leak pre-stored handles on failure.

Signed-off-by: Varsha Prasad <varshaprasad96@gmail.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>

* fix(credentials): add workspace/provider UUID to credential backend paths

Include workspace and provider ID in credential backend object paths to ensure
cross-workspace uniqueness and prevent credential collision (GATOR-1806c9be-01).

- Updated credential driver proto to include workspace and provider_id fields
- Modified Vault driver to include workspace/provider_id in managed_secret_path
- Modified Kubernetes Secrets driver to include workspace/provider_id in
  credential_owner_id and managed_secret_name
- Updated all credential runtime calls to pass workspace/provider_id
- Updated tests to use the new signatures

This prevents two workspaces sharing the same external credential store from
colliding on provider names, which was a critical security issue (CWE-639).

* fix(credentials): preserve provider-level expiration for handle-backed credentials

Compute effective expiration from both provider and driver values using the
earliest non-zero timestamp and skip expired values before insertion
(GATOR-1806c9be-02).

- Modified resolve_provider_handles to check provider credential_expires_at_ms
- Skip expired credentials during resolution instead of returning them
- Use effective expiration (min of provider and driver) in resolution results
- Fix inference.rs to preserve earliest expiration when merging

This ensures handle-backed credentials respect the same expiration semantics
as inline credentials.

* fix(credentials): stage refresh changes under new handles before validation

Stage credential replacements under new immutable handles instead of reusing
existing handles to prevent overwriting committed values before validation/CAS
(GATOR-1806c9be-03).

- Stage credentials with empty existing_handles map to force new handle creation
- Validate and CAS before the new values are committed to backend storage
- Delete old handles only after successful CAS
- On CAS failure, delete only the newly staged handles
- This prevents CWE-362/CWE-367 race conditions where failed refreshes could
  still modify or delete the active credential

The fix ensures that a rejected refresh cannot modify the backend object still
referenced by the committed provider record.

* fix(credentials): add timeouts to credential driver RPCs

Apply configured timeouts to both startup capability negotiation and runtime
RPCs to prevent indefinite hangs (GATOR-1806c9be-05).

- Add DEFAULT_CREDENTIAL_DRIVER_RPC_TIMEOUT_SECS constant (30s)
- Apply timeout to GetCapabilities during startup connection
- Apply timeout to all runtime RPCs (store, delete, resolve)
- Use tokio::time::timeout to bound the entire GetCapabilities operation
  during startup, not just the socket connection
- Return contextual deadline errors on timeout

This prevents a faulty or overloaded driver from hanging gateway operations
indefinitely.

* fix(credentials): fix test to use consistent workspace/provider identity

The Kubernetes auth Vault resolve test was constructing a managed path with
test-workspace/test-provider-id but sending default/prov-123 in the request,
causing validation to reject the request (GATOR-18e32351-01).

- Update test to use test-workspace and test-provider-id in the request to
  match the logical_path construction
- This ensures the test exercises the intended code path and validates
  Kubernetes auth resolution properly

The test now passes and correctly validates identity enforcement.

* fix(credentials): use unique staging ID for refresh to avoid overwrites

Stage refresh replacements under genuinely distinct immutable handles using
a unique staging ID to prevent overwriting committed values (GATOR-1806c9be-03).

- Generate a unique staging ID using UUID for each refresh operation
- Use this staging ID when storing credentials instead of the real provider ID
- Pass the same staging ID during cleanup on failure to delete only staged objects
- This ensures deterministic paths (Vault) and object names (K8s) don't collide
  with the committed provider's credentials

The fix prevents failed refreshes from silently replacing active credentials
or breaking providers by deleting still-referenced backend objects.

* fix(credentials): wrap credential driver RPCs in local timeouts

Add local tokio::time::timeout wrappers around credential driver RPCs to
bound non-compliant or stalled UDS peers (GATOR-1806c9be-05).

- Wrap StoreCredential, DeleteCredential, and ResolveCredentials in local timeouts
- Return contextual deadline_exceeded errors when timeouts occur
- Keep existing gRPC timeout metadata for compliant implementations
- GetCapabilities during startup was already wrapped in previous commit

This ensures a faulty local driver cannot hang gateway operations indefinitely,
even if it accepts the connection but never responds to the RPC.

* fix(credentials): preserve ownership for staged refreshes

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* fix(credentials): bound startup capability probe

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* test(provider): authenticate credential handler requests

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* fix(ci): grant actions read to credential driver e2e

Signed-off-by: Seth Jennings <sjenning@redhat.com>

---------

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Signed-off-by: Varsha Prasad <varshaprasad96@gmail.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>
Signed-off-by: Seth Jennings <sjenning@redhat.com>
Co-authored-by: Taylor Mutch <taylormutch@gmail.com>
Co-authored-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>
2026-08-05 16:29:41 +00:00
..

OpenShell Architecture

OpenShell runs autonomous AI agents in sandboxed environments with explicit policy, credential, identity, and network boundaries. The target architecture is built around three stable runtime components: the CLI, the Gateway, and the Supervisor.

The CLI, SDK, and TUI provide user-facing access. The gateway is the authenticated control plane: it owns API access, durable state, policy and settings delivery, provider and inference configuration, and relay coordination. The supervisor runs inside every sandbox workload and is the local security boundary. It launches the agent as a restricted child process and enforces policy where process identity, filesystem access, network egress, and runtime credentials are visible.

Infrastructure-specific work sits behind integration boundaries. Compute, credentials, control-plane identity, and sandbox identity each have a driver or adapter boundary so OpenShell can integrate with native runtimes, secret stores, identity providers, and workload identity systems without moving those concerns into the core gateway or sandbox model.

flowchart TB
    subgraph USER["User Interfaces"]
        CLI["CLI"]
        SDK["SDK"]
        TUI["TUI"]
    end

    subgraph CP["Control Plane"]
        GW["Gateway core"]
        DB[("Shared persistence")]
        COMPUTE["Compute"]
        CREDS["Credentials"]
        CPIDENT["Control-plane identity"]
        SIDENT["Sandbox identity"]
        CDRV["Compute driver"]
        CRDRV["Credentials driver"]
        CPIDRV["Control-plane identity driver"]
        SIDRV["Sandbox identity driver"]
    end

    subgraph INFRA["Integrated Infrastructure"]
        RUNTIME["Docker / Podman / Kubernetes / VM"]
        SECRETSTORE["Eg: Keychain / Secret Service / Vault / Kubernetes Secrets"]
        IDP["Eg: mTLS / OIDC / Local identity"]
        WORKLOADID["Eg: SPIFFE / Gateway-issued workload identity"]
    end

    subgraph DP["Sandbox Data Plane"]
        SUP["Supervisor"]
        PROXY["Policy proxy"]
        ROUTER["Inference router"]
        POLICY["OPA policy engine"]
        AGENT["Restricted agent process"]
    end

    CLI -->|"gRPC / HTTP"| GW
    SDK -->|"gRPC / HTTP"| GW
    TUI -->|"gRPC / HTTP"| GW

    GW --> DB
    GW --> COMPUTE
    GW --> CREDS
    GW --> CPIDENT
    GW --> SIDENT

    COMPUTE -->|"gRPC / UDS"| CDRV
    CREDS -->|"gRPC / UDS"| CRDRV
    CPIDENT -->|"gRPC / UDS"| CPIDRV
    SIDENT -->|"gRPC / UDS"| SIDRV

    CDRV --> RUNTIME
    CRDRV --> SECRETSTORE
    CPIDRV --> IDP
    SIDRV --> WORKLOADID
    RUNTIME -->|"provisions workload"| SUP

    SUP -->|"outbound control, config, logs, relay"| GW
    SUP -->|"spawn + restrict"| AGENT
    AGENT -->|"all ordinary egress"| PROXY
    PROXY -->|"evaluate"| POLICY
    PROXY -->|"allowed traffic"| EXT["External services"]
    PROXY -->|"inference.local"| ROUTER
    ROUTER -->|"managed inference"| MODEL["Inference backends"]

Core Boundaries

Component Boundary
CLI, SDK, TUI User-facing management surfaces. They talk to the gateway and do not need to know which infrastructure drivers are active.
Gateway Authenticated control plane, API server, durable state, policy and settings delivery, provider and inference config, supervisor session ownership, and relay coordination.
Compute subsystem Sandbox lifecycle semantics: creation, deletion, watching, reconciliation, and state transitions. Platform provisioning details belong to the compute driver.
Credentials subsystem Logical provider and credential resolution. Secret storage and platform-native credential access belong to credentials drivers.
Control-plane identity Authentication and authorization for users, operators, and API clients. External identity verification belongs to identity drivers.
Sandbox identity Workload identity for supervisors and sandbox-to-sandbox authorization. Identity issuance or verification belongs to sandbox identity drivers.
Supervisor Sandbox-local security boundary. It prepares isolation, fetches config, injects credentials, runs relay endpoints, starts the proxy, and launches restricted agent processes.
Policy proxy Mandatory egress path for agent traffic. It enforces destination, binary identity, SSRF, TLS/L7, credential injection, and inference interception rules.
Inference router Sandbox-local forwarding for https://inference.local to configured model backends.

Integrating with the Ecosystem

OpenShell should integrate with infrastructure ecosystems instead of replacing them. The core value is safe, policy-enforced agent execution. Runtimes, schedulers, secret stores, identity providers, workload identity systems, image pipelines, storage, and GPU or device exposure should remain owned by the platforms that already provide them.

The gateway owns OpenShell control-plane semantics: sandbox state, lifecycle ordering, policy and settings resolution, credential mapping, authorization, inference configuration, and relay coordination. Drivers translate those semantics into platform-native operations. They should stay thin, preserve native behavior by default, and report platform lifecycle events back through the shared contracts.

The supervisor owns OpenShell sandbox semantics. Filesystem policy, process privilege reduction, network proxying, inference interception, credential injection, security logging, and gateway relay behavior should remain consistent across runtimes.

This keeps OpenShell usable in local single-player setups, Kubernetes deployments, VM-backed sandboxes, and future third-party environments. A new integration should make OpenShell feel like a well-behaved member of that ecosystem.

Gateways and Sandboxes

The gateway and sandbox split control-plane authority from runtime enforcement. The gateway owns durable platform state: sandboxes, policy revisions, runtime settings, provider records, inference configuration, session records, and authorization decisions. A sandbox owns the local execution boundary: process identity, filesystem access, network egress, credential injection, local logs, and the agent child process.

The relationship is supervisor initiated. Each sandbox supervisor connects outbound to a known gateway endpoint, authenticates as a sandbox workload, and keeps a live session open for control traffic and relays. This avoids requiring every compute driver to solve gateway-to-sandbox reachability through pod IPs, bridge networks, port mappings, NAT traversal, or bespoke tunnels. The common runtime requirement is narrower: the supervisor must be able to reach the gateway.

The gateway delivers desired state; the sandbox applies it locally. Policy, settings, credentials, and inference routes flow from the gateway to the supervisor. The supervisor validates and applies what can change at runtime, keeps last-known-good config when refresh fails, and leaves static isolation controls in place until the sandbox is recreated.

Live operations use the same authenticated gateway-supervisor relationship. Config refresh, policy updates, credential delivery, log push, connect, exec, file sync, and relay setup are multiplexed over supervisor sessions. If a session drops, the sandbox may keep running, but live operations fail or become unreachable until the supervisor reconnects and reconciles state.

Architecture Docs

Architecture docs are short subsystem overviews. User-facing how-to content lives in docs/. Implementation notes that only matter to one crate belong in that crate's README.md.

Document Purpose
Gateway Gateway control plane, auth, APIs, persistence, settings, and relay coordination.
Sandbox Sandbox supervisor, child process isolation, proxy, credentials, inference, connect, and logs.
Security Policy Policy model, enforcement layers, policy updates, policy advisor, and security logging.
Compute Runtimes Docker, Podman, Kubernetes, VM, sandbox images, and runtime-specific responsibilities.
Build Build artifacts, CI/E2E, docs site validation, and release packaging.
Google Vertex AI Provider Implementation reference for the google-vertex-ai provider, from CLI through gateway to sandbox.

rfc/ vs architecture/

Broad design proposals start as GitHub issues. If maintainers decide a proposal needs broad consensus, they assign an RFC number from the issue and the RFC lives in rfc/. Once an RFC is adopted, appropriate details should be written back to architecture docs.

architecture/ serves as the canonical reference for OpenShell's design and architecture.

rfc serves to help facilitate discussion and ensure the few changes that need that level of review are appropriately designed. These are useful for understanding the context in which certain architecture designs were made.