Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.env
target/
.git/
.github/
*.md
!README.md
docs/
install.sh
install-openclaw-fork.sh
packages/
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target/
.DS_Store
.env
58 changes: 58 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Secure Agent Wallet (SAW) — a local signing service for AI agents. Private keys never leave the daemon process. Policy rules gate every signing request. All access is via Unix domain sockets.

## Build & Test Commands

```bash
# Build all Rust crates
cargo build --workspace

# Run all tests (CI runs this)
cargo test --workspace

# Run a single test by name
cargo test --workspace -- test_name

# Run tests for a specific crate
cargo test -p saw # CLI crate
cargo test -p saw-daemon # daemon crate

# Build the Node.js client
cd packages/saw && npm install && npm run build

# Run Node.js client tests (requires vitest)
cd packages/saw && npm test

# Docker (SAW daemon + OpenClaw gateway)
./setup.sh # build, onboard (first run), start
docker compose logs -f # watch startup
```

## Architecture

**Rust workspace** with two crates:

- **`crates/saw-cli`** (`saw` binary) — Key generation (EVM via k256/secp256k1, Solana via ed25519-dalek), policy management (YAML with `serde_yaml`, `deny_unknown_fields`), directory layout setup. The `lib.rs` exposes core types (`Chain`, `Policy`, `WalletPolicy`, `GenKeyResult`) and functions (`gen_key`, `validate_policy`, `get_address`, `list_wallets`, `install_layout`). The `cli` module parses args manually (no clap).

- **`crates/saw-daemon`** (`saw-daemon` binary) — AF_UNIX server that accepts JSON requests, enforces policy checks (chain allowlist, address allowlist, value limits, contract call restriction, rate limiting), signs transactions, and writes to `audit.log`. The `Server` struct in `lib.rs` holds mutable rate-limit state. Public API: `serve_once`, `serve_n`, `serve_forever`, `serve_forever_with_shutdown`. Daemon tests use `serve_n` or `serve_once` with temp directories.

- **`packages/saw`** (`@daydreamsai/saw` npm package) — TypeScript client that talks to the daemon over the Unix socket. Built with tsup, tested with vitest.

**Data layout** (default `~/.saw/`):
- `keys/{evm,sol}/<wallet>.key` — raw binary private keys (0600)
- `policy.yaml` — per-wallet signing rules (strict schema, unknown fields rejected)
- `audit.log` — append-only request log
- `saw.sock` — Unix domain socket (0660)

**Key patterns:**
- Both crates duplicate `Chain`, `WalletPolicy`, `Policy` types (no shared crate). The daemon re-reads `policy.yaml` on every request.
- Wallet names are validated: alphanumeric, `_`, `-`, 1-64 chars.
- EVM signing uses `secp256k1` crate for recoverable ECDSA (not k256's signer). EIP-1559 (type 2) transactions only.
- Solana signing operates on raw message bytes — no transaction parsing or policy enforcement beyond rate limits.
- Tests create isolated temp directories and use the library API directly (not shelling out to binaries).
- No async runtime — the daemon uses blocking I/O with non-blocking listener accept loop and `set_read_timeout`.
77 changes: 77 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# ── Stage 1: Build SAW binaries from source ──────────────────────────────
FROM rust:1.85-slim-bookworm AS builder

RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config libssl-dev \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src
COPY Cargo.toml Cargo.lock ./
COPY crates/ crates/

RUN cargo build --release --workspace \
&& strip target/release/saw target/release/saw-daemon

# ── Stage 2: Runtime image ────────────────────────────────────────────────
FROM node:22-slim AS runtime

# SAW configuration (override at runtime)
ENV SAW_ROOT=/opt/saw \
SAW_SOCKET=/run/saw/saw.sock \
SAW_WALLET=main \
SAW_CHAIN=evm \
SAW_POLICY_TEMPLATE=conservative

# OpenClaw paths
ENV HOME=/home/node \
XDG_CONFIG_HOME=/home/node/.openclaw

# OpenClaw build args
ARG OPENCLAW_REF=v2026.2.9-dreamclaw.14
ARG OPENCLAW_RELEASE_REPO=https://github.com/RedBeardEth/clawdbot

# System deps: curl for tarball download, git for fallback source install
RUN apt-get update && apt-get install -y --no-install-recommends \
curl git ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Force HTTPS for GitHub (npm git tries SSH by default)
RUN git config --global url."https://github.com/".insteadOf "ssh://git@github.com/"

# Install SAW binaries
COPY --from=builder /src/target/release/saw /usr/local/bin/saw
COPY --from=builder /src/target/release/saw-daemon /usr/local/bin/saw-daemon

# Install OpenClaw globally (try release tarball first, fallback to git)
RUN set -eux; \
tarball_url="${OPENCLAW_RELEASE_REPO}/releases/download/${OPENCLAW_REF}/openclaw-${OPENCLAW_REF}.tgz"; \
if curl -fsSL --head --connect-timeout 5 "$tarball_url" >/dev/null 2>&1; then \
echo "==> Installing OpenClaw from release tarball"; \
npm install -g "$tarball_url"; \
else \
echo "==> Installing OpenClaw from git source"; \
npm install -g "git+${OPENCLAW_RELEASE_REPO}.git#${OPENCLAW_REF}"; \
fi \
&& npm cache clean --force \
&& openclaw --version

# Create SAW system user and agent group; add node to saw-agent for socket access
RUN groupadd --system saw-agent \
&& useradd --system --no-create-home --shell /usr/sbin/nologin --groups saw-agent saw \
&& usermod -aG saw-agent node

# Initialize SAW data directory and OpenClaw config directory
RUN saw install --root "$SAW_ROOT" \
&& mkdir -p /run/saw /home/node/.openclaw/workspace \
&& chown -R saw:saw "$SAW_ROOT" /run/saw

COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

EXPOSE 18789

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD test -S "$SAW_SOCKET" || exit 1

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["openclaw", "gateway", "--bind", "lan", "--port", "18789", "--allow-unconfigured"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

CMD hardcodes --bind lan --port 18789, ignoring the env vars defined above.

OPENCLAW_GATEWAY_BIND and OPENCLAW_GATEWAY_PORT are set in the ENV block and can be overridden at runtime, but the CMD uses literal values. A user changing the env vars would get no effect on the gateway's actual bind/port.

Switch to shell-form or reference the variables:

Proposed fix
-CMD ["openclaw", "gateway", "--bind", "lan", "--port", "18789", "--allow-unconfigured"]
+CMD ["sh", "-c", "exec openclaw gateway --bind \"$OPENCLAW_GATEWAY_BIND\" --port \"$OPENCLAW_GATEWAY_PORT\" --allow-unconfigured"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CMD ["openclaw", "gateway", "--bind", "lan", "--port", "18789", "--allow-unconfigured"]
CMD ["sh", "-c", "exec openclaw gateway --bind \"$OPENCLAW_GATEWAY_BIND\" --port \"$OPENCLAW_GATEWAY_PORT\" --allow-unconfigured"]
🤖 Prompt for AI Agents
In `@Dockerfile` at line 78, The CMD currently hardcodes "--bind lan --port 18789"
which ignores the environment variables OPENCLAW_GATEWAY_BIND and
OPENCLAW_GATEWAY_PORT; update the Dockerfile CMD that runs "openclaw gateway" to
reference those env vars (e.g., switch to shell form or an exec form that
invokes a shell) so the process uses $OPENCLAW_GATEWAY_BIND and
$OPENCLAW_GATEWAY_PORT at runtime; ensure the CMD still includes the other flags
like "--allow-unconfigured" and "--bind/--port" placeholders are replaced with
the env var references.

Loading