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 values
crates/openshell-sandbox/src/process.rs:119-120 — scrub_sensitive_env() then inject_provider_env() with placeholders only
crates/openshell-sandbox/src/l7/relay.rs:282 — relay_http_request_with_resolver rewrites 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_KEY is visible in ps aux on 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_profile in nemoclaw-start.sh:87-107 writes 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
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 connect or openshell sandbox exec — no credential arguments needed
- The agent uses
inference.local for model calls, which routes through the gateway where credential injection happens server-side
- No
NVIDIA_API_KEY export, no SSH command string with secrets, no write_auth_profile needed
The provider system is generic — it works for any credential, not just inference. No OpenShell changes are required to support this.
Summary
The Telegram bridge in
scripts/telegram-bridge.js:108SSHs into the sandbox and passesNVIDIA_API_KEYas a plaintext shell environment variable: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:GetSandboxProviderEnvironment)NVIDIA_API_KEY=openshell:resolve:env:NVIDIA_API_KEY) in the child process environmentThe real credential never exists as a readable string inside the sandbox.
printenv,/proc/self/environ, andps auxall 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 levelWhat 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:
NVIDIA_API_KEYis visible inps auxon both host and sandboxwrite_auth_profileinnemoclaw-start.sh:87-107writes an auth profile referencing this env var, creating a second readable path to the credentialThis directly undoes the correct behavior already present in the onboarding flow (
onboard.js:1801-1809), which explicitly deletesNVIDIA_API_KEYfrom the sandbox environment.Relationship to existing issues
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.Suggested fix
The bridge should not inject any credentials into the sandbox. Instead:
openshell provider create(already done during onboarding)openshell sandbox connectoropenshell sandbox exec— no credential arguments neededinference.localfor model calls, which routes through the gateway where credential injection happens server-sideNVIDIA_API_KEYexport, no SSH command string with secrets, nowrite_auth_profileneededThe provider system is generic — it works for any credential, not just inference. No OpenShell changes are required to support this.