DEV Community

Cover image for Localhost-Only Security: Why Self-Hosted AI Wallets Should Never Bind to 0.0.0.0
Wallet Guy
Wallet Guy

Posted on

Localhost-Only Security: Why Self-Hosted AI Wallets Should Never Bind to 0.0.0.0

Localhost-Only Security: Why Self-Hosted AI Wallets Should Never Bind to 0.0.0.0

Self-hosted AI wallets give you complete control over your keys and infrastructure — but that control means nothing if your wallet daemon is accidentally exposed to the world. If you're running an AI agent that holds real funds, the network binding of your wallet service is the first line of defense, and getting it wrong is the kind of mistake you only make once.

The Problem With "Just Open It Up"

When developers spin up a new service, there's a tempting shortcut: bind to 0.0.0.0 so it's reachable from anywhere. For a static site or a read-only API, this is fine. For a service that holds private keys and can authorize cryptocurrency transactions on behalf of an AI agent, it's a serious risk.

The stakes here are different from a misconfigured web server. If someone reaches your wallet daemon unexpectedly — from another container, another machine on your LAN, or through a misconfigured firewall — they're not reading your blog posts. They're potentially talking to an API that can sign and broadcast transactions. The damage is irreversible. On-chain transactions don't have a "dispute with your bank" option.

This is why self-hosting your AI wallet infrastructure isn't just about privacy and sovereignty (though those matter too). It's about applying the correct security defaults for what the service actually does.

Why Self-Hosting in the First Place?

Before getting into the technical details, it's worth grounding the philosophy here.

When you use a hosted Wallet-as-a-Service, someone else holds your keys or at minimum has the infrastructure-level ability to inspect, delay, or block your agent's transactions. You're trusting a third party's uptime, their security practices, their API rate limits, and their business continuity. For many use cases, that trade-off is fine. But for developers who care about sovereignty — running their own infrastructure, keeping their own keys, knowing exactly what software is executing — the hosted model has a ceiling.

Self-hosting an AI wallet is roughly the crypto equivalent of running your own email server. It was once seen as unnecessarily complicated. Now, with containers making it a one-command operation, the calculus has changed. You get full control over where your data lives, what network it's exposed to, and who can reach it.

WAIaaS is an open-source self-hosted Wallet-as-a-Service built specifically for AI agents. It runs as a local daemon, exposes a REST API over HTTP, and is designed from the start to live on 127.0.0.1 — not the public internet.

The Default: 127.0.0.1:3100

The WAIaaS Docker Compose configuration binds to 127.0.0.1:3100:3100 by default. That colon-separated IP address at the front is doing critical work. It means the port is only reachable from the local machine — not from your LAN, not from another Docker network unless you explicitly configure it, and certainly not from the internet.

services:
  daemon:
    image: ghcr.io/minhoyoo-iotrust/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

You'll notice WAIAAS_DAEMON_HOSTNAME=0.0.0.0 in the environment. This is not a contradiction — it means the daemon inside the container listens on all interfaces within the container's network namespace. The binding restriction (127.0.0.1:3100) is enforced by Docker at the host level. The process inside the container doesn't need to know it's isolated; Docker handles the network boundary.

This is the correct pattern: let the container runtime enforce network exposure, not hope the application gets it right.

Three Auth Layers Before Any Transaction Moves

Network isolation is layer zero. WAIaaS also enforces a 3-layer authentication model that separates concerns between the system administrator, the fund owner, and the AI agent itself.

# masterAuth — system administrator (wallet creation, session management, policies)
-H "X-Master-Password: my-secret-password"

# sessionAuth — AI agent (transactions, balance queries, DeFi actions)
-H "Authorization: Bearer wai_sess_eyJhbGciOiJIUzI1NiJ9..."

# ownerAuth — fund owner (transaction approval, kill switch recovery)
-H "X-Owner-Signature: <ed25519-or-secp256k1-signature>"
-H "X-Owner-Message: <signed-message>"
Enter fullscreen mode Exit fullscreen mode

The AI agent only ever holds a session token — a JWT that can be scoped, time-limited, and revoked. It never touches the master password. The master password (hashed with Argon2id) is only used for administrative operations: creating wallets, managing sessions, configuring policies. The fund owner, who holds an ed25519 or secp256k1 keypair, is the only one who can approve transactions that exceed the policy thresholds or recover from a compromised session.

Even if an attacker somehow reaches the API on localhost (which requires they already have code execution on your machine, at which point you have bigger problems), they still need to know which token or password authorizes which operation. The separation of roles limits blast radius significantly.

Production Secrets Without Environment Variables in Plain Text

For production deployments, WAIaaS supports Docker Secrets — a mechanism where secret values live in files rather than environment variables, reducing the risk of accidental exposure through docker inspect, log scraping, or process listings.

# 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 docker-compose.secrets.yml overlay patches in the secret mounts without changing your base configuration. This means your base docker-compose.yml stays clean and can be committed to version control, while the secrets file stays out of it.

Auto-Provision for First Boot

If you're spinning up WAIaaS for the first time and don't want to manually set a master password, the auto-provision flag generates one for you and writes it to a recovery key file:

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

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

The recovery key approach is practical: you get a running system immediately, and you can harden the password later using waiaas set-master from the CLI. The important thing is to delete the recovery key file once you've stored the password somewhere safe.

The Policy Engine as Your Second Network

Even after you've locked down network access and authentication, there's still the question of what the AI agent is allowed to do once it has a valid session token. This is where WAIaaS's policy engine becomes part of the security architecture rather than just a feature.

The policy engine enforces 21 policy types with 4 security tiers. Default-deny is the enforced behavior: if ALLOWED_TOKENS is not configured, token transfers are blocked. If CONTRACT_WHITELIST is not configured, contract calls are blocked.

Here's a policy that limits spending with tiered escalation:

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

The four tiers — INSTANT, NOTIFY, DELAY, and APPROVAL — mean that small transactions go through immediately, mid-range transactions send a notification to the owner, larger ones queue for a time delay (and are cancellable during that window), and anything above the threshold requires explicit human approval. This is enforced through a 7-stage transaction pipeline.

The AI agent has no way to bypass this. The pipeline enforces policy at the server level, not at the client level. The agent submits a transaction, and the pipeline decides what happens next based on the configured policies.

A Minimal Self-Hosted Setup in Five Steps

Here's the complete path from zero to a running, locally-bound AI wallet daemon:

Step 1: Install the CLI

npm install -g @waiaas/cli
Enter fullscreen mode Exit fullscreen mode

Step 2: Initialize and start

waiaas init --auto-provision
waiaas start
Enter fullscreen mode Exit fullscreen mode

Step 3: Create wallets and sessions in one command

waiaas quickset --mode mainnet
Enter fullscreen mode Exit fullscreen mode

Step 4: Connect to your AI agent (Claude Desktop)

waiaas mcp setup --all
Enter fullscreen mode Exit fullscreen mode

This auto-registers all wallets with Claude Desktop by writing the MCP configuration. The MCP server exposes 45 tools to the AI agent — everything from balance checks to DeFi actions — all proxied through your local daemon with session authentication enforced.

Step 5: Set a real master password

waiaas set-master
Enter fullscreen mode Exit fullscreen mode

Once done, delete the auto-generated recovery key.

What "Your Keys, Your Server" Actually Means

Self-hosted infrastructure means the keys never leave your machine. WAIaaS runs entirely on your hardware, with data stored in a named Docker volume that only you control. There's no telemetry, no cloud sync, no third-party service that holds or proxies your keys.

The WAIaaS monorepo is a 15-package open-source codebase. The daemon, the CLI, the MCP server, the TypeScript SDK, the Python SDK, the admin UI — all of it is in the repository and auditable. If you want to know exactly what's running on your machine when you start the daemon, you can read the source.

This also means the Docker image comes from a specific, versioned registry path (ghcr.io/minhoyoo-iotrust/waiaas:latest) that you can verify. The entrypoint script handles auto-provisioning and Docker Secrets in a way you can inspect before running.

The daemon also runs as a non-root user (UID 1001) inside the container. This is a small detail with meaningful consequences: even if a bug in the daemon led to container escape, the process wouldn't have root privileges on the host. Defense in depth applies at every layer.

What's Next

If you want to go deeper on the policy engine and understand how to configure the full set of 21 policy types for your specific use case, the policy configuration documentation is the right next step. For teams who want to integrate WAIaaS into an existing AI agent framework rather than Claude Desktop, the OpenClaw plugin and the TypeScript or Python SDK are worth looking at — the SDK has over 40 methods and zero external dependencies.

The full source is at https://github.com/minhoyoo-iotrust/WAIaaS and the official site with documentation is at https://waiaas.ai. If you're running this in a homelab or a privacy-focused setup, the GitHub issues and discussions are the right place to share what you're building and ask questions.

Top comments (0)