Skip to content

OpenAI Realtime browser session sends unresolved keychain SecretRef as API key #72120

@ctbritt

Description

@ctbritt

Summary

OpenAI Realtime / Talk browser-session auth appears to read process.env.OPENAI_API_KEY directly and can pass an unresolved SecretRef string to OpenAI instead of resolving it first.

In a macOS Keychain-backed setup where the gateway LaunchAgent environment contains:

OPENAI_API_KEY=keychain:openclaw:OPENAI_API_KEY

Realtime browser session creation fails with OpenAI receiving the literal/placeholder value rather than the resolved sk-... key.

Observed behavior

talk.realtime.session fails with:

OpenAI Realtime browser session failed (401): Incorrect API key provided: keychain********************_KEY
[type=invalid_request_error, code=invalid_api_key]

The same Keychain entry resolves successfully via:

security find-generic-password -s openclaw -a OPENAI_API_KEY -w

and direct OpenAI API probes with the resolved value succeed, so this is not an invalid OpenAI key.

Likely cause

In the installed 2026.4.24 runtime, the OpenAI Realtime provider path uses roughly:

const apiKey = config.apiKey || process.env.OPENAI_API_KEY;

before POSTing to:

https://api.openai.com/v1/realtime/client_secrets

normalizeProviderConfig() correctly uses normalizeResolvedSecretInputString() for provider config apiKey, but the process.env.OPENAI_API_KEY fallback is not SecretRef-aware. If OpenClaw-managed launch env intentionally stores keychain refs rather than plaintext values, this fallback path passes the unresolved ref to OpenAI.

The same pattern also appears in the realtime voice bridge creation path.

Expected behavior

OpenAI Realtime should resolve supported SecretRef/env/keychain references before using the API key, or fail locally with a clear unresolved-secret error instead of sending the placeholder to OpenAI.

Ideally:

  • plugins.entries.voice-call.config.realtime.providers.openai.apiKey SecretRefs are resolved before use.
  • process.env.OPENAI_API_KEY fallback treats known OpenClaw SecretRef forms, such as keychain:openclaw:OPENAI_API_KEY, as references to resolve rather than literal keys.
  • The resolved key should be cached in-process for the gateway lifetime/session rather than calling Keychain on every Realtime request.
  • Plaintext secrets should not be written to openclaw.json or LaunchAgent plists as the workaround.

Environment

  • OpenClaw: 2026.4.24
  • macOS LaunchAgent-managed gateway
  • Node: 25.9.0
  • OpenAI Realtime browser session / Talk path
  • Secrets stored in macOS Keychain and exposed to OpenClaw launch env as keychain:openclaw:<NAME> refs

Local workaround tested

A local hot patch resolved keychain:<service>:<account> via /usr/bin/security find-generic-password ... -w, cached the result in-process, and used that resolved value in both:

  • Realtime browser session creation
  • Realtime voice bridge creation

That eliminated the obvious broken code path without writing plaintext secrets to disk.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions