-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
Summary
The Telegram bridge in scripts/telegram-bridge.js:108 SSHs into the sandbox and passes NVIDIA_API_KEY as a plaintext shell environment variable:
const cmd = `export NVIDIA_API_KEY=${shellQuote(API_KEY)} && nemoclaw-start openclaw agent ...`;
const proc = spawn("ssh", ["-T", "-F", confPath, `openshell-${SANDBOX}`, cmd], { ... });This bypasses the OpenShell provider system's credential isolation mechanism, which is specifically designed to prevent real secrets from ever existing as readable strings inside the sandbox.
How OpenShell credential isolation works
OpenShell's provider system (openshell provider create) stores credentials server-side in the gateway. When a sandbox starts, the runtime:
- Fetches credential values from the gateway via gRPC (
GetSandboxProviderEnvironment) - Replaces every real value with an opaque placeholder (e.g.,
NVIDIA_API_KEY=openshell:resolve:env:NVIDIA_API_KEY) in the child process environment - When the sandbox process makes an HTTP request containing a placeholder in a header value, the L7 proxy rewrites the placeholder to the real secret only in the outbound bytes on the wire
The real credential never exists as a readable string inside the sandbox. printenv, /proc/self/environ, and ps aux all show only the placeholder.
Relevant OpenShell source:
crates/openshell-sandbox/src/secrets.rs—SecretResolver::from_provider_env()splits credentials into placeholders + real valuescrates/openshell-sandbox/src/process.rs:119-120—scrub_sensitive_env()theninject_provider_env()with placeholders onlycrates/openshell-sandbox/src/l7/relay.rs:282—relay_http_request_with_resolverrewrites headers at the wire level
What the Telegram bridge does instead
The bridge runs on the host and SSHes into the sandbox with the raw key embedded in the command string. This means:
- The real
NVIDIA_API_KEYis visible inps auxon both host and sandbox - The key is exported as a plain env var readable by any process in the sandbox
- A compromised agent can exfiltrate it through any allowed egress path
write_auth_profileinnemoclaw-start.sh:87-107writes an auth profile referencing this env var, creating a second readable path to the credential
This directly undoes the correct behavior already present in the onboarding flow (onboard.js:1801-1809), which explicitly deletes NVIDIA_API_KEY from the sandbox environment.
Relationship to existing issues
- CRITICAL: NVIDIA API Key Exposed in Process Arguments and Terminal Output #579 identified
NVIDIA_API_KEYexposure in process arguments across multiple files. Many of those locations have been fixed, but the Telegram bridge path (telegram-bridge.js:108) remains active and is the most severe remaining instance because it injects the key into the sandbox. - chore: unify credential passing pattern across all integrations #616 proposed unifying credential patterns but framed the fix as standardizing on env vars. The recommended fix here is to stop using raw env vars entirely for credentials and use the OpenShell provider system instead.
Suggested fix
The bridge should not inject any credentials into the sandbox. Instead:
- Register the inference provider via
openshell provider create(already done during onboarding) - The bridge invokes the agent via
openshell sandbox connectoropenshell sandbox exec— no credential arguments needed - The agent uses
inference.localfor model calls, which routes through the gateway where credential injection happens server-side - No
NVIDIA_API_KEYexport, no SSH command string with secrets, nowrite_auth_profileneeded
The provider system is generic — it works for any credential, not just inference. No OpenShell changes are required to support this.