Repository navigation
feat(sandbox): supervisor should periodically refresh provider credentials for running sandboxes #1264
Description
Problem Statement
When a provider's credentials are updated (e.g., a rotated API key), running sandboxes never observe the change. The supervisor calls GetSandboxProviderEnvironment exactly once at startup (crates/openshell-sandbox/src/lib.rs:272-301) and stores the result in an immutable Arc<SecretResolver>. The run_policy_poll_loop refreshes policy and settings every 10 seconds but never re-fetches provider credentials.
This means:
- Rotating a provider's API key requires restarting every sandbox using that provider.
- Short-lived tokens (OAuth2, SSO) cannot be consumed by long-running sandboxes without external workarounds.
- The operator/gateway correctly propagates Secret changes to the gateway DB, but the last mile — gateway DB to running supervisor — is broken.
Issue #896 proposes a comprehensive credential refresh lifecycle as part of Provider Profiles (including oauth2 and external strategies), and #1171 addresses attach/detach semantics for new process launches. However, neither addresses the simpler immediate gap: the supervisor's existing SecretResolver should be refreshable at runtime so that credential rotation works for the current provider model without waiting for the full Provider v2 redesign.
Proposed Design
Add periodic credential refresh to the supervisor's existing polling loop. The change is minimal and incremental:
- Change
SecretResolverstorage fromArc<SecretResolver>toArc<RwLock<SecretResolver>>(orarc_swap::ArcSwap<SecretResolver>for lock-free reads). The proxy hot-path reads credentials on every request; aRwLockwith rare writes is acceptable, butArcSwapwould be zero-cost for the read path. - Add a
refresh_provider_environmentcall torun_policy_poll_loop(or a parallel loop). Every N seconds (e.g., 60s, configurable), callGetSandboxProviderEnvironment, diff the result against the currentSecretResolver, and swap if changed. - The proxy reads the current
SecretResolversnapshot per-request. WithArcSwap, this is a single atomic load — no contention with the refresh writer. - No changes to child process environment variables. Already-running processes keep their placeholder env vars (
openshell:resolve:env:*). The placeholders don't change — only the real values in theSecretResolvermapping change. Since resolution happens at the proxy layer at request time, the agent seamlessly uses the new credentials on the next API call without restart. - No new RPCs needed. The existing
GetSandboxProviderEnvironmentunary RPC is sufficient. The supervisor already calls it; it just needs to call it more than once.
Startup (existing):
fetch_provider_environment() → SecretResolver (immutable)
Proposed:
fetch_provider_environment() → ArcSwap<SecretResolver>
poll loop (every 60s):
new_env = fetch_provider_environment()
if new_env != current_env:
secret_resolver.swap(SecretResolver::from_provider_env(new_env))
This is fully backward-compatible:
- The
staticrefresh strategy from Enhanced Provider Management #896 describes exactly this behavior ("takes effect on the next proxied request or next supervisor provider-cache refresh") — this PR would implement that cache refresh. - No proto changes. No gateway changes. Supervisor-only.
- Child processes are unaffected (env vars contain placeholders, not secrets).
Alternatives Considered
- Push-based streaming RPC (e.g.,
WatchProviderEnvironment): More complex, requires gateway-side subscription management, and doesn't align with the existing pull-based polling model used for policy. The polling approach is simpler and consistent with existing architecture. - Restart sandbox on credential change: Current workaround, but breaks long-running agent sessions and is unacceptable for production use with token rotation.
- **Wait for full Provider v2 (**Enhanced Provider Management #896): The roadmap is comprehensive but large. The credential refresh gap exists today with the v1 provider model and can be fixed incrementally without conflicting with v2 work.
- External sidecar/daemon that restarts sandboxes: Adds operational complexity and still disrupts running sessions unnecessarily.
Agent Investigation
Codebase analysis of crates/openshell-sandbox/:
src/lib.rs:272-301:fetch_provider_environmentcalled once at startup, result stored inHashMap<String, String>.src/lib.rs:303-304:SecretResolver::from_provider_env()creates an immutableSecretResolver, wrapped inArc<SecretResolver>.src/secrets.rs:67-90:SecretResolveris#[derive(Clone)]with a plainHashMap<String, String>field. NoRwLock, no mutation methods.src/lib.rs:2148-2307:run_policy_poll_looppollsGetSandboxConfigfor policy/settings only — never credentials.proto/openshell.proto:84: Comment explicitly says "called by sandbox supervisor at startup" — this is a documentation issue, not a protocol limitation.- The proxy reads
SecretResolveron every HTTP request viaresolve()method — this is the natural injection point for refreshed credentials. ArcSwapis not currently inCargo.tomlbut is a single dependency add. Alternatively,tokio::sync::watchcould broadcast new resolvers to proxy tasks.
Related: #896 (Provider v2 roadmap, credential refresh lifecycle), #1171 (attach/detach for running sandboxes)
Activity
johntmyers commentedon May 8, 2026
Thanks @kon-angelo this seems reasonable and an easy win.
Does this refer to the
UpdateProviderRequestAPI? Just making sure as there aren't any other mechanisms for updating secret values. This is somewhat orthogonal to providers v2 because providers v2 would still benefit from being able to refresh any updated credential values.It's also worth noting this only is for credential k/v pairs that already exist on the provider object.
Feel free to put up a PR if you have capacity.
kon-angelo commentedon May 15, 2026
/assign
johntmyers commentedon May 15, 2026
I think this has now been implemented as part of the Providers work.
Relevant commits:
39d9d66a feat(providers): add credential refresh foundationadded provider refresh state/configuration, refresh strategies, provider env revisions, and the gateway-side refresh plumbing.6651c780 fix(providers): refresh sandbox provider credentialsimplemented the supervisor/runtime side: the sandbox now tracksprovider_env_revision, re-fetchesGetSandboxProviderEnvironmentfrom the poll loop when it changes, and swaps provider credential resolver state for later proxied requests/new launches.That appears to cover the main request in this issue: running sandboxes no longer depend only on the one-time startup provider environment fetch, and provider credential updates can be observed without restarting the sandbox. As designed here, this refreshes proxy-resolved provider credentials; it does not mutate concrete env vars inside already-running processes.
@kon-angelo does that match what you were looking for here? If so, are you comfortable closing this issue?
kon-angelo commentedon May 15, 2026
@johntmyers I think you are correct. Thank you for the implementation and apologies for not handling this earlier. I will test and report if anything is missing.