Securing OpenClaw Credentials

post Main Image

What happens when your AI assistant can read your .env file?

DISREGARG THIS ARTICLE FOR NOW: this actually is not secure at all LOOOOOL. Will keep at it and will update the article ASAP with a much better solution: I think we need to play with different users...

Note:

This article is a follow-up to the original OpenClaw setup guide. It addresses a security concern that became apparent after the initial setup was complete.

In the previous guide, we configured OpenClaw as a systemd service to keep it running persistently on our remote VM. However, a critical security issue emerged: the API credentials stored in the .env file were accessible to any process running as our user — including AI assistants with file access. This follow-up documents the solution.


The Problem

After setting up OpenClaw as described in the original guide, a significant security concern came to light. The API keys and Telegram bot tokens stored in ~/.openclaw/.env were accessible to any process running under our user account (demo_user).

This became particularly relevant when using AI coding assistants that have access to the filesystem. Even though the assistant was designed to help with development, it could potentially read the .env file and extract credentials — essentially handing over the keys to the kingdom.

The root cause was simple: the systemd service ran as the same user who owned the credentials, with no isolation between the two.

Securing credentials concept

Why the Systemd Approach Failed

Several attempts were made to secure the credentials within the systemd framework:

  1. Environment variables in service file: The credentials were loaded via EnvironmentFile in the systemd unit. While the file could be protected with restrictive permissions, any process running as the same user could still read it.
  2. Encrypted credentials with decryption script: We attempted to use openssl to encrypt the credentials and decrypt them at service startup. This required a password — but systemd services don't have access to interactive terminals.
  3. TTY allocation: We tried allocating a TTY to the service to enable password prompting. This failed because systemd services are fundamentally designed to run detached from any terminal.
  4. systemd-ask-password: This tool is designed for systemd but proved unreliable in practice, with the password prompt either not appearing or failing silently.

The core issue: systemd is built for automated, unattended service management — not for interactive password entry. There is no clean way to require a password at service startup within the systemd paradigm.

Technical security concept

The Solution: Manual Start with Encrypted Credentials

The solution combines encryption with manual operation:

  1. Encrypt credentials: Store API keys in an encrypted file using openssl
  2. Manual start: Start OpenClaw manually when needed, entering the decryption password interactively
  3. No auto-restart: Since we must enter a password anyway, automatic restarts don't make sense

This approach means OpenClaw only runs when we're actively using it — and the credentials remain encrypted on disk at all other times.


Step-by-Step Implementation

Step 1: Stop and Remove the Systemd Service

sudo systemctl stop openclaw
sudo systemctl disable openclaw
sudo rm -f /etc/systemd/system/openclaw.service
sudo systemctl daemon-reload

Step 2: Create Encrypted Credentials File

First, create a directory for the secrets:

mkdir -p ~/.openclaw/secrets
chmod 700 ~/.openclaw/secrets

Create a temporary file with your credentials:

cat > ~/.openclaw/secrets/creds.txt << 'EOF'
DEEPSEEK_API_KEY=sk-your-actual-api-key-here
TELEGRAM_BOT_TOKEN=your-telegram-bot-token-here
EOF

Encrypt the file (you'll be prompted for a password):

openssl enc -aes-256-cbc -salt -pbkdf2 -in ~/.openclaw/secrets/creds.txt -out ~/.openclaw/secrets/creds.enc

Securely remove the plaintext file:

shred -u ~/.openclaw/secrets/creds.txt
chmod 600 ~/.openclaw/secrets/creds.enc

Step 3: Create the Decryption Script

cat > ~/.openclaw/secrets/decrypt.sh << 'EOF'
#!/bin/bash
read -sp "Enter decryption password: " PASSWORD
echo "$PASSWORD" | openssl enc -aes-256-cbc -d -salt -pbkdf2 -pass stdin -in ~/.openclaw/secrets/creds.enc 2>/dev/null
EOF
chmod 700 ~/.openclaw/secrets/decrypt.sh

Step 4: Create the Controller Script

Create a script that handles starting, stopping, and checking the status of OpenClaw:

cat > ~/openclawctl << 'EOF'
#!/bin/bash

OPENCLAW_DIR="$HOME/.openclaw"
PID_FILE="$OPENCLAW_DIR/openclaw.pid"
LOG_FILE="$OPENCLAW_DIR/openclaw.log"
DECRYPT_SCRIPT="$OPENCLAW_DIR/secrets/decrypt.sh"

mkdir -p "$OPENCLAW_DIR"

case "$1" in
    start)
        if [ -f "$PID_FILE" ]; then
            PID=$(cat "$PID_FILE")
            if kill -0 "$PID" 2>/dev/null; then
                echo "OpenClaw is already running (PID: $PID)"
                exit 1
            else
                rm -f "$PID_FILE"
            fi
        fi

        echo "Starting OpenClaw..."

        DECRYPTED=$(bash "$DECRYPT_SCRIPT")
        if [ $? -ne 0 ] || [ -z "$DECRYPTED" ]; then
            echo "Failed to decrypt credentials"
            exit 1
        fi

        eval "$(echo "$DECRYPTED" | grep -v "^#" | grep -v "^$")"

        if [ -z "$DEEPSEEK_API_KEY" ] && [ -z "$TELEGRAM_BOT_TOKEN" ]; then
            echo "Failed to load credentials"
            exit 1
        fi

        nohup env DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" TELEGRAM_BOT_TOKEN="$TELEGRAM_BOT_TOKEN" /usr/bin/npx openclaw gateway >> "$LOG_FILE" 2>&1 &
        echo $! > "$PID_FILE"

        sleep 3
        if kill -0 $(cat "$PID_FILE") 2>/dev/null; then
            echo "OpenClaw started (PID: $(cat "$PID_FILE"))"
        else
            echo "Failed to start OpenClaw - check $LOG_FILE"
            rm -f "$PID_FILE"
            exit 1
        fi
        ;;

    stop)
        if [ ! -f "$PID_FILE" ]; then
            echo "OpenClaw is not running"
            exit 1
        fi

        PID=$(cat "$PID_FILE")
        kill "$PID" 2>/dev/null

        for i in 1 2 3 4 5; do
            if ! kill -0 "$PID" 2>/dev/null; then
                break
            fi
            sleep 1
        done

        if kill -0 "$PID" 2>/dev/null; then
            echo "Force killing OpenClaw..."
            kill -9 "$PID" 2>/dev/null
        fi

        rm -f "$PID_FILE"
        echo "OpenClaw stopped"
        ;;

    status)
        if [ ! -f "$PID_FILE" ]; then
            echo "OpenClaw is not running"
            exit 1
        fi

        PID=$(cat "$PID_FILE")
        if kill -0 "$PID" 2>/dev/null; then
            echo "OpenClaw is running (PID: $PID)"
        else
            echo "OpenClaw is not running (stale PID file)"
            rm -f "$PID_FILE"
        fi
        ;;

    logs)
        if [ -f "$LOG_FILE" ]; then
            tail -50 "$LOG_FILE"
        else
            echo "No log file found"
        fi
        ;;

    *)
        echo "Usage: $0 {start|stop|status|logs}"
        exit 1
        ;;
esac
EOF
chmod +x ~/openclawctl

Step 5: Remove the Old .env File

shred -u ~/.openclaw/.env

Usage

Now instead of using systemctl, use the openclawctl script:

# Start OpenClaw (will prompt for password)
~/openclawctl start

# Check status
~/openclawctl status

# View logs
~/openclawctl logs

# Stop OpenClaw
~/openclawctl stop

The password prompt appears in the terminal, and OpenClaw runs in the background using nohup — meaning it persists even after the SSH session closes.


Security Trade-offs

This approach significantly improves security:

However, there are trade-offs:


Conclusion

The transition from systemd service to manual operation with encrypted credentials addresses the original security concern. While less convenient than automatic startup, this approach ensures that API keys and bot tokens remain protected even from processes running as the same user.

The key insight is that the convenience of automatic startup directly conflicted with the security requirement of keeping credentials encrypted. By accepting manual operation, we achieve both security and peace of mind.