DEV Community

Cover image for Argon2id Security for Self-Hosted AI Wallets: CLI Master Password Best Practices
Wallet Guy
Wallet Guy

Posted on

Argon2id Security for Self-Hosted AI Wallets: CLI Master Password Best Practices

Argon2id Security for Self-Hosted AI Wallets: CLI Master Password Best Practices

Self-hosted AI wallets live or die by one question: who controls the master password? If you're running WAIaaS on your own server, you've already made the right call by keeping your AI agent's private keys off someone else's infrastructure — but that decision only pays off if the password protecting those keys is treated with the same seriousness as the keys themselves. This post walks through exactly how WAIaaS handles master password security and what you should do to harden it from day one.

Why the Master Password Is the Root of Everything

In a self-hosted wallet setup, the master password isn't just an admin credential — it's the cryptographic root from which everything else derives. WAIaaS uses Argon2id to protect it, which is the current best-practice algorithm for password hashing. Argon2id is memory-hard, meaning brute-force attacks are expensive in both time and RAM, not just CPU cycles. That matters enormously when your wallet is internet-accessible, even behind a VPN.

The master password gates the masterAuth layer — one of three distinct authentication methods in WAIaaS:

  • masterAuth (Argon2id) — system administrator: creates wallets, manages sessions, sets policies
  • ownerAuth (SIWS/SIWE) — fund owner: approves transactions, uses the kill switch
  • sessionAuth (JWT HS256) — AI agents: executes transactions within policy bounds

Your AI agent never touches the master password. It operates on short-lived session tokens. The master password is only needed for administrative operations: provisioning wallets, setting policies, and creating those session tokens. That separation is intentional and important. Compromise a session token and an attacker is bounded by your policies. Compromise the master password and it's a different conversation entirely.

The Auto-Provision Path: Getting Started Without Foot Guns

If you're spinning up WAIaaS for the first time, the --auto-provision flag is your friend. It generates a cryptographically random master password on first start and writes it to a recovery file, so you don't have to invent a password under pressure during setup.

npm install -g @waiaas/cli
waiaas init --auto-provision     # Generates random master password → recovery.key
waiaas start                     # No password prompt
waiaas quickset                  # Creates wallets + sessions automatically
waiaas set-master                # (Later) Harden password, then delete recovery.key
Enter fullscreen mode Exit fullscreen mode

That last step — waiaas set-master — is critical and easy to skip. The auto-generated password in recovery.key is strong, but it's sitting on disk in plaintext. Your hardening checklist after initial setup should be:

  1. Run waiaas set-master to replace the auto-generated password with one you've chosen and can store in a proper secret manager
  2. Verify the new password works by running a test command (e.g., waiaas status)
  3. Delete recovery.key from disk
  4. Store the new password in a password manager or hardware-backed secret store — never in a .env file committed to version control

The same pattern applies to Docker deployments. Auto-provision works identically:

docker run -d \
  --name waiaas \
  -p 127.0.0.1:3100:3100 \
  -v waiaas-data:/data \
  -e WAIAAS_AUTO_PROVISION=true \
  ghcr.io/waiaas/waiaas:latest

# Retrieve auto-generated master password
docker exec waiaas cat /data/recovery.key
Enter fullscreen mode Exit fullscreen mode

After retrieving that key, treat it like a root SSH key: copy it somewhere safe, then replace it.

Docker Secrets: The Production-Grade Approach

Environment variables are convenient but they leak. They show up in docker inspect, in process tables, in container logs if you're not careful. For a self-hosted setup that you're actually going to trust with real funds, Docker Secrets is the right tool.

WAIaaS's Docker entrypoint supports Docker Secrets natively, and there's a dedicated docker-compose.secrets.yml overlay for production deployments. The workflow looks like this:

# Create secret files
mkdir -p secrets
echo "your-secure-password" > secrets/master_password.txt
chmod 600 secrets/master_password.txt

# Deploy with secrets overlay
docker compose -f docker-compose.yml -f docker-compose.secrets.yml up -d
Enter fullscreen mode Exit fullscreen mode

The 600 permissions on the secrets file matter. It means only the owning user can read it — not other users on the same system, not processes running as different UIDs. The WAIaaS Docker image itself runs as UID 1001 (non-root), which means even if something inside the container misbehaves, it can't escalate to root to read files it shouldn't.

The base Docker Compose configuration for reference:

services:
  daemon:
    image: ghcr.io/waiaas/waiaas:latest
    container_name: waiaas-daemon
    ports:
      - "127.0.0.1:3100:3100"
    volumes:
      - waiaas-data:/data
    environment:
      - WAIAAS_DATA_DIR=/data
      - WAIAAS_DAEMON_HOSTNAME=0.0.0.0
    env_file:
      - path: .env
        required: false
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3100/health"]
      interval: 30s
      timeout: 5s
      start_period: 10s
      retries: 3

volumes:
  waiaas-data:
    driver: local
Enter fullscreen mode Exit fullscreen mode

Notice the port binding: 127.0.0.1:3100:3100. That's loopback-only by default, which means the API is not exposed to your network unless you explicitly make it so. If you need remote access, put a reverse proxy with TLS in front of it rather than changing the bind address to 0.0.0.0 directly.

What masterAuth Actually Protects

It helps to be concrete about what operations require the master password, because that scope defines your threat model.

# Create a wallet (masterAuth)
curl -X POST http://127.0.0.1:3100/v1/wallets \
  -H "Content-Type: application/json" \
  -H "X-Master-Password: my-secret-password" \
  -d '{"name": "trading-wallet", "chain": "solana", "environment": "mainnet"}'

# Create a session for AI agent (masterAuth)
curl -X POST http://127.0.0.1:3100/v1/sessions \
  -H "Content-Type: application/json" \
  -H "X-Master-Password: my-secret-password" \
  -d '{"walletId": "<wallet-uuid>"}'

# Create policy (masterAuth)
curl -X POST http://127.0.0.1:3100/v1/policies \
  -H "Content-Type: application/json" \
  -H "X-Master-Password: my-secret-password" \
  -d '{
    "walletId": "<wallet-uuid>",
    "type": "SPENDING_LIMIT",
    "rules": {
      "instant_max_usd": 100,
      "notify_max_usd": 500,
      "delay_max_usd": 2000,
      "delay_seconds": 900,
      "daily_limit_usd": 5000
    }
  }'
Enter fullscreen mode Exit fullscreen mode

Once wallets, sessions, and policies are set up, your AI agent operates entirely on its session token. The master password doesn't appear in any agent workflow. This is by design — you do your administrative work, lock down the master password in your secret manager, and your agent runs without ever needing it.

The session token gets passed as a standard Bearer token:

# Check wallet balance (sessionAuth)
curl http://127.0.0.1:3100/v1/wallet/balance \
  -H "Authorization: Bearer wai_sess_eyJhbGciOiJIUzI1NiJ9..."
Enter fullscreen mode Exit fullscreen mode

That session token has its own security properties: per-session TTL, maxRenewals, and absoluteLifetime controls. If a session token is compromised, you revoke it without touching the master password. The blast radius is bounded.

Policy as Defense-in-Depth

Even with a strong master password and properly isolated session tokens, you want policies acting as a backstop. WAIaaS has a policy engine with 21 policy types and a default-deny stance — transactions are blocked unless explicitly allowed.

The most important policy to set early is SPENDING_LIMIT, which maps to the four security tiers:

  • INSTANT — execute immediately
  • NOTIFY — execute immediately, send notification
  • DELAY — queue for N seconds, then execute (cancellable window)
  • APPROVAL — require human approval via WalletConnect or Telegram

For a self-hosted setup where you're the only operator, you might start conservative:

curl -X POST http://127.0.0.1:3100/v1/policies \
  -H "Content-Type: application/json" \
  -H "X-Master-Password: my-secret-password" \
  -d '{
    "walletId": "<wallet-uuid>",
    "type": "SPENDING_LIMIT",
    "rules": {
      "instant_max_usd": 100,
      "notify_max_usd": 500,
      "delay_max_usd": 2000,
      "delay_seconds": 900,
      "daily_limit_usd": 5000
    }
  }'
Enter fullscreen mode Exit fullscreen mode

With these rules, anything under $100 executes immediately. $100-$500 executes but you get notified. $500-$2,000 sits in a queue for 15 minutes — you can cancel it. Above $2,000, your agent can't proceed without you explicitly approving it via WalletConnect or a signing channel. That's a meaningful safety net even if a session token were somehow misused.

You can also dry-run any transaction before it touches the network, which is useful during setup to verify your policies are behaving as expected:

curl -X POST http://127.0.0.1:3100/v1/transactions/send \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer wai_sess_<token>" \
  -d '{
    "type": "TRANSFER",
    "to": "recipient-address",
    "amount": "0.1",
    "dryRun": true
  }'
Enter fullscreen mode Exit fullscreen mode

The Self-Hosted Philosophy in Practice

Running your own wallet infrastructure is the crypto equivalent of hosting your own email server, but WAIaaS makes it genuinely practical rather than a weekend project that never quite works. One Docker command gets you a daemon with:

  • Argon2id master password hashing
  • Non-root container execution (UID 1001)
  • Docker Secrets support
  • Health checks
  • Loopback-only port binding by default
  • Named volume data persistence
git clone https://github.com/waiaas/WAIaaS.git
cd WAIaaS
docker compose up -d
Enter fullscreen mode Exit fullscreen mode

You own the data directory. You own the keys. There's no hosted service receiving your transaction history, no third-party API key required to make basic wallet operations work. The tradeoff is that you're responsible for your own backups (WAIaaS has backup create, backup list, and restore CLI commands for this) and your own uptime. For most self-hosters on community forums like r/selfhosted and r/homelab, that tradeoff is exactly the point.

Quick Hardening Checklist

If you're setting up WAIaaS today, here's the condensed version:

  1. Use auto-provision first, then immediately run waiaas set-master to replace the generated password with one stored in your password manager
  2. Delete recovery.key after confirming your new password works
  3. Use Docker Secrets (docker-compose.secrets.yml) rather than passing the master password as an environment variable in production
  4. Keep the port binding loopback-only (127.0.0.1:3100:3100) and put TLS in front via a reverse proxy if you need remote access
  5. Set a SPENDING_LIMIT policy before your agent starts executing transactions — default-deny means your agent can't do anything without explicit permission anyway, but limits give you a time window to react if something goes wrong
  6. Create session tokens with appropriate TTLs using waiaas set-master to configure session lifetime parameters — a session token with a short TTL limits exposure windows

The CLI gives you 20 commands covering the full lifecycle from init through backup and restore. The status and wallet info commands are useful for verifying your setup is in the state you think it's in before you hand session tokens to an agent.

What's Next

The security model described here — Argon2id master auth, session-scoped agent tokens, policy-gated transactions — is the foundation everything else in WAIaaS builds on. Once you have a hardened self-hosted instance running, the natural next steps are connecting it to your AI agent via MCP or the TypeScript/Python SDKs, and configuring DeFi policies for the specific protocols your agent will interact with.

If you want to dig into the full setup or read the source, everything is at https://github.com/waiaas/WAIaaS. The project site with documentation is at https://waiaas.ai. No hosted service required — you've got the keys.

Top comments (0)