< Back to all hacks

#37 API Keys Out of ps Output

Dirty COW
Problem
API keys (Kimi, Telegram, OpenAI) visible in clear text in ps -eo args when passed as proot arguments.
Solution
Load keys from /root/.openclaw/env file inside proot instead of command line arguments.
Lesson
Never pass secrets as command-line arguments. Any user on the system can read them via /proc/[pid]/cmdline.

Context

The original PocketClaw start script passed API keys directly as command-line arguments to the proot process. This worked, but had a serious security problem: on any Unix system, the command line of every running process is world-readable. Any process or user can inspect it via ps -eo args or by reading /proc/[pid]/cmdline.

On the Moto E2, this means any Termux plugin, any app with shell access, or anyone connected via ADB could see the full Kimi API key, Telegram bot token, OpenAI key, and Moonshot key in plain text. The ps output looked like this:

proot --rootfs=/data/data/com.termux/files/usr/var/lib/proot-distro/installed-rootfs/ubuntu \
  /bin/bash -c "export TELEGRAM_BOT_TOKEN='8360xxxxx:AAHxxxxxxx' \
  MOONSHOT_API_KEY='sk-xxxxxxxxxxxxxxxx' \
  OPENAI_API_KEY='sk-xxxxxxxxxxxxxxxx' && ..."

Every key, fully visible. On a shared system or a compromised device, this is a credential leak waiting to happen.

Implementation

The fix: store keys in a file inside the proot rootfs, source it at runtime, and never pass secrets on the command line.

Create the env file:

# Inside proot (or write from Termux to the rootfs path):
cat > /root/.openclaw/env << 'EOF'
MOONSHOT_API_KEY=sk-xxxxxxxxxxxxxxxx
TELEGRAM_BOT_TOKEN=8360xxxxx:AAHxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxx
OPENCLAW_MODEL_PROVIDER=kimi
EOF

# Lock down permissions — readable only by owner:
chmod 600 /root/.openclaw/env

Update the start script to source the file instead of passing keys inline:

# Before (INSECURE — keys visible in ps):
proot --rootfs=$ROOTFS /bin/bash -c "
  export TELEGRAM_BOT_TOKEN='8360...' MOONSHOT_API_KEY='sk-...' && \
  node dist/cli.js gateway run --port 9000
"

# After (SECURE — keys loaded from file):
proot --rootfs=$ROOTFS /bin/bash -c "
  . /root/.openclaw/env && \
  export MOONSHOT_API_KEY TELEGRAM_BOT_TOKEN OPENAI_API_KEY && \
  node dist/cli.js gateway run --port 9000
"

After the native migration (no more proot), the same pattern is used directly:

# In $PREFIX/bin/start-openclaw (native stack):
. $PREFIX/var/lib/proot-distro/installed-rootfs/ubuntu/root/.openclaw/env
export MOONSHOT_API_KEY TELEGRAM_BOT_TOKEN OPENAI_API_KEY OPENCLAW_MODEL_PROVIDER
exec $PREFIX/bin/node22-icu $PREFIX/lib/node_modules/openclaw/dist/cli.js gateway run --port 9000

Verification

# Check that ps no longer shows any keys:
ps -eo args | grep -i "api_key\|bot_token\|sk-"
# Expected: no output (or only the grep command itself)

# Verify the env file has correct permissions:
ls -la /root/.openclaw/env
# Expected: -rw------- 1 root root ... env

# Verify keys are loaded in the Node.js process:
# (from inside the gateway, via the dashboard API)
curl -s http://localhost:9000/api/status | grep -o '"provider":"[^"]*"'
# Expected: "provider":"kimi" (proves OPENCLAW_MODEL_PROVIDER loaded)

# Double-check /proc/[pid]/cmdline is clean:
cat /proc/$(pgrep -f openclaw-gateway)/cmdline | tr '\0' ' '
# Expected: node path with no API keys visible

Gotchas

  • Environment variables set via export in the shell are still visible in /proc/[pid]/environ, but this file is readable only by the process owner (or root). This is significantly more secure than /proc/[pid]/cmdline which is world-readable
  • The env file must use export VAR separately from the sourcing. The pattern . /root/.openclaw/env && export VAR1 VAR2 sources the file (setting shell variables) and then exports them to the environment. Without the explicit export, child processes (Node.js) cannot see the variables
  • If the env file has Windows line endings (CRLF), the values will include a trailing \r character. This causes API authentication to fail with cryptic "invalid key" errors. Always ensure the file uses Unix line endings: tr -d '\r' < env > env.clean && mv env.clean env
  • chmod 600 only protects against other Unix users. On Android, all Termux processes run as the same uid (u0_a1), so any Termux session can read the file. The protection is against other Android apps (different uids) and ADB shell (uid 2000, but can use run-as to access Termux files)
  • Hack #50 (API Key Management) later added a dashboard UI for editing these keys without SSH access. The underlying storage remained the same env file

Result

AspectBeforeAfter
Keys in ps output4 keys visible0 keys visible
Keys in /proc/cmdlineWorld-readableNot present
Keys in /proc/environN/A (inline)Owner-read only
File permissionsN/Achmod 600
Key rotationEdit start scriptEdit env file

A simple fix with outsized security impact. The env file pattern became the standard for all secret management in PocketClaw, later extended with the dashboard KEYS page (Hack #50) for browser-based key management. The principle holds universally: never pass secrets as command-line arguments, on any system, in any language.