The provider registry
unify/common/runtime_oauth.py
is the single source of truth for what an “OAuth provider” is at runtime.
OAuthProviderMetadata describes each provider — canonical name, aliases,
and the secret names that hold its state — and _OAUTH_PROVIDER_METADATA
registers the two built-ins:
| Provider | Aliases | Secrets |
|---|---|---|
google | gmail, google_workspace, drive | GOOGLE_ACCESS_TOKEN, GOOGLE_REFRESH_TOKEN, GOOGLE_TOKEN_EXPIRES_AT, GOOGLE_GRANTED_SCOPES |
microsoft | msft, ms365, microsoft_365, graph | MICROSOFT_ACCESS_TOKEN, MICROSOFT_REFRESH_TOKEN, MICROSOFT_TOKEN_EXPIRES_AT, MICROSOFT_GRANTED_SCOPES |
_resolve_oauth_provider normalizes aliases, so actor code can say
get_oauth_access_token("drive") and land on Google. Adding a new OAuth
provider means adding a metadata entry here — the sync allowlist, proxy
env, and prompt documentation all derive from it.
Two functions, one trust boundary
The module exposes two token accessors, and the difference between them is the security model:get_provider_access_token(provider, min_ttl_seconds=300)— returns the real bearer token. Trusted-runtime only: used byWorkspaceEmailManagerand by the proxy itself. If the token is missing or within the TTL window of expiry, it forces a secret sync from Orchestra first.get_oauth_access_token(provider, min_ttl_seconds=300)— the sandbox-facing function. Returns a proxy nonce, never a real token. This is the symbol injected intoexecute_codeglobals.
get_provider_access_token_optimistic (no pre-expiry
gate — the proxy uses it per-request and retries once on 401),
refresh_provider_access_token (force a re-sync from Orchestra — note it
does not call the provider’s token endpoint), and
get_refresh_token_oauth_env_overlay (the env-var overlay for
subprocesses). get_oauth_prompt_context renders the actor-facing
documentation block for all of this.
Split storage in SecretManager
unify/secret_manager/secret_manager.py
mirrors assistant secrets down from Orchestra
(_sync_assistant_secrets hits the admin assistant endpoint and filters
through _resolve_secret_allowlist, which unions the OAuth names from
refresh_token_oauth_secret_names). The storage rule:
- Sensitive — the raw access/refresh tokens
(
_sensitive_oauth_token_names) live only in the in-memory_oauth_tokensdict, read viaget_oauth_token. They are never written to the Secrets context,.env, oros.environ. - Non-sensitive metadata —
*_TOKEN_EXPIRES_ATand*_GRANTED_SCOPES— is mirrored to the environment so that scope checks and expiry hints work everywhere, including inside the sandbox.
_sync_workspace_file_policy → PolicyStore.set_policies — see
Provider proxy).
Sync triggers. sync_assistant_secrets_if_stale is a debounced gate
called before every execute_code (from
unify/actor/code_act_actor.py
with a 60-second TTL), forced on assistant-update events (see
unify/conversation_manager/domains/managers_utils.py), and forced by
get_provider_access_token when a token is missing or near expiry. The
practical upshot: reconnecting an account in the Console propagates to a
live assistant within a minute without a restart.
Refresh model. The runtime never performs OAuth refresh grants. A
platform job refreshes tokens against Google/Microsoft and writes them to
Orchestra; the runtime’s “refresh” is always pull the newer token down.
This keeps refresh-token custody in exactly one place.
Keeping the sandbox token-free
Several mechanisms cooperate so that no raw token is observable from LLM-authored code:- Injection without exposure —
unify/function_manager/execution_env.py(create_execution_globals) placesget_oauth_access_token— the nonce-returning one — into sandbox globals. - Environment scrubbing —
provider_token_env_keysinunify/provider_proxy/session.pylists the sensitive keys, andbuild_sandbox_envstrips them while overlaying the proxy variables (WORKSPACE_PROXY_URL,WORKSPACE_PROXY_TOKEN,MICROSOFT_GRAPH_BASE,GOOGLE_DRIVE_BASE,GOOGLE_API_BASE). Subprocess execution sites inunify/function_manager/function_manager.pyandshell_session.pyapply it;ensure_proxy_runningadditionally scrubs any lingering raw tokens from the parentos.environ. - Virtual-env isolation — functions running in dedicated venvs
(
unify/function_manager/venv_runner.py) get aget_oauth_access_tokenshim that RPCs the parent process (namespaceruntime, handled byFunctionManager._handle_venv_rpc), so even a separate interpreter never receives the real token.
Scope awareness
Granted scopes are deliberately visible (they’re metadata, not credentials). The actor prompt —_EXTERNAL_APP_INTEGRATION in
unify/actor/prompt_builders.py
— teaches the model a checking discipline: before calling an API, read
GOOGLE_GRANTED_SCOPES / MICROSOFT_GRANTED_SCOPES (space-separated raw
scope strings; Microsoft short names are prefixed with
https://graph.microsoft.com/ except offline_access). If the secret is
present but the needed scope is absent, don’t call the API — tell the user
to reconnect with the extra feature from the Console. That behavioral rule
is what turns a missing scope into a helpful message instead of a 403.
Tests
The behavior on this page is pinned bytests/common/test_runtime_oauth.py,
tests/secret_manager/test_oauth_tokens.py, and
tests/function_manager/test_runtime_oauth_bridge.py (the venv RPC
bridge).