Files
PaperClipAI/docs/guides/openclaw-docker-setup.md
T
Nicky LeachandPaperclip fdb9a4880d fix(security): route paperclipai CLI guidance through safe npx form (CWE-78) (#11400)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Paperclip provides CLI commands and guidance for operators and
agents
> - The `pnpm paperclipai` script can pass argument values through a
shell
> - Shell re-parsing can execute command substitutions inside quoted
values
> - This pull request routes guidance through inert-argv `npx
paperclipai` commands and adds regression coverage
> - The benefit is safer operator guidance across documentation and
runtime hints

## Linked Issues or Issue Description

This pull request fixes a command-injection-class defect in Paperclip
CLI guidance.

**What happened?**

The `pnpm paperclipai <sub> --flag "$VALUE"` form can re-parse argument
values through a shell. A command substitution inside a quoted value can
execute on the host.

**Expected behavior**

Paperclip guidance must pass CLI values as inert argument values.
Host-derived values must not appear in copyable commands.

**Steps to reproduce**

1. Run a Paperclip guidance command that uses the `pnpm paperclipai`
script.
2. Provide a quoted value that contains a command substitution.
3. Observe that the shell can evaluate the substitution before the CLI
starts.
4. Compare the result with the `npx paperclipai` form.

**Paperclip version or commit**

`5670984b75d109950c968542a0111ebb6967f4da`

**Deployment mode**

All deployment modes that show or use the affected CLI guidance.

**Installation method**

Built from source and installed CLI guidance.

**Agent adapter(s) involved**

Not adapter-specific (core bug).

**Database mode**

Not database-related.

**Access context**

Both.

**Additional context**

The earlier merged PR
[#11343](https://github.com/paperclipai/paperclip/pull/11343) used the
unsafe `pnpm exec paperclipai` form. This fresh PR replaces that
guidance with the safe `npx paperclipai` form.

## What Changed

- Standardize documentation and runtime hints on `npx paperclipai`.
- Remove the broken `pnpm exec paperclipai` guidance.
- Use a static `<host>` placeholder in private-hostname guidance.
- Add regression tests for unsafe forms, continued lines, static hosts,
and offline guidance.

## Verification

- `git diff --check
origin/master...origin/fix/paperclipai-cli-npx-safe-invocation` passes.
- The branch adds `server/src/__tests__/cli-invocation-safety.test.ts`
and updates private-hostname tests.
- CI must run the new tests, typecheck, lint, and build checks.
- Local Vitest execution was not available because this worktree has no
installed Vitest binary.

## Risks

- The change affects operator and agent documentation text.
- The runtime hints now show `<host>` instead of a request-derived host
value.
- No database schema or migration changes exist.
- CI will detect any missed unsafe invocation or type error.

## Model Used

OpenAI GPT-5, exact model ID `gpt-5`, with tool use and code-review
assistance. The model used repository inspection, Git operations, and PR
preparation.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] CI ran the test suites and they pass; local test execution was
unavailable in this worktree
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I addressed all Greptile and reviewer comments before requesting
merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-14 22:11:16 -07:00

350 lines
11 KiB
Markdown

# Running OpenClaw in Docker (Local Development)
How to get OpenClaw running in a Docker container for local development and testing the Paperclip OpenClaw adapter integration.
## Automated Join Smoke Test (Recommended First)
Paperclip includes an end-to-end join smoke harness:
```bash
pnpm smoke:openclaw-join
```
The harness automates:
- invite creation (`allowedJoinTypes=agent`)
- OpenClaw agent join request (`adapterType=openclaw`)
- board approval
- one-time API key claim (including invalid/replay claim checks)
- wakeup callback delivery to a dockerized OpenClaw-style webhook receiver
By default, this uses a preconfigured Docker receiver image (`docker/openclaw-smoke`) so the run is deterministic and requires no manual OpenClaw config edits.
Permissions note:
- The harness performs board-governed actions (invite creation, join approval, wakeup of the new agent).
- In authenticated mode, provide board/operator auth or the run exits early with an explicit permissions error.
## One-Command OpenClaw Gateway UI (Manual Docker Flow)
To spin up OpenClaw in Docker and print a host-browser dashboard URL in one command:
```bash
pnpm smoke:openclaw-docker-ui
```
Default behavior is zero-flag: you can run the command as-is with no pairing-related env vars.
What this command does:
- clones/updates `openclaw/openclaw` in `/tmp/openclaw-docker`
- builds `openclaw:local` (unless `OPENCLAW_BUILD=0`)
- writes isolated smoke config under `~/.openclaw-paperclip-smoke/openclaw.json` and Docker `.env`
- pins agent model defaults to OpenAI (`openai/gpt-5.2` with OpenAI fallback)
- starts `openclaw-gateway` via Compose (with required `/tmp` tmpfs override)
- probes and prints a Paperclip host URL that is reachable from inside OpenClaw Docker
- waits for health and prints:
- `http://127.0.0.1:18789/#token=...`
- disables Control UI device pairing by default for local smoke ergonomics
Environment knobs:
- `OPENAI_API_KEY` (required; loaded from env or `~/.secrets`)
- `OPENCLAW_DOCKER_DIR` (default `/tmp/openclaw-docker`)
- `OPENCLAW_GATEWAY_PORT` (default `18789`)
- `OPENCLAW_GATEWAY_TOKEN` (default random)
- `OPENCLAW_BUILD=0` to skip rebuild
- `OPENCLAW_OPEN_BROWSER=1` to auto-open the URL on macOS
- `OPENCLAW_DISABLE_DEVICE_AUTH=1` (default) disables Control UI device pairing for local smoke
- `OPENCLAW_DISABLE_DEVICE_AUTH=0` keeps pairing enabled (then approve browser with `devices` CLI commands)
- `OPENCLAW_MODEL_PRIMARY` (default `openai/gpt-5.2`)
- `OPENCLAW_MODEL_FALLBACK` (default `openai/gpt-5.2-chat-latest`)
- `OPENCLAW_CONFIG_DIR` (default `~/.openclaw-paperclip-smoke`)
- `OPENCLAW_RESET_STATE=1` (default) resets smoke agent state on each run to avoid stale auth/session drift
- `PAPERCLIP_HOST_PORT` (default `3100`)
- `PAPERCLIP_HOST_FROM_CONTAINER` (default `host.docker.internal`)
### Authenticated mode
If your Paperclip deployment is `authenticated`, provide auth context:
```bash
PAPERCLIP_AUTH_HEADER="Bearer <token>" pnpm smoke:openclaw-join
# or
PAPERCLIP_COOKIE="your_session_cookie=..." pnpm smoke:openclaw-join
```
### Network topology tips
- Local same-host smoke: default callback uses `http://127.0.0.1:<port>/webhook`.
- Inside OpenClaw Docker, `127.0.0.1` points to the container itself, not your host Paperclip server.
- For invite/onboarding URLs consumed by OpenClaw in Docker, use the script-printed Paperclip URL (typically `http://host.docker.internal:3100`).
- If Paperclip rejects the container-visible host with a hostname error, allow it from host:
```bash
npx paperclipai allowed-hostname host.docker.internal
```
Then restart Paperclip and rerun the smoke script.
- Docker/remote OpenClaw: prefer a reachable hostname (Docker host alias, Tailscale hostname, or public domain).
- Authenticated/private mode: ensure hostnames are in the allowed list when required:
```bash
npx paperclipai allowed-hostname <host>
```
## Prerequisites
- **Docker Desktop v29+** (with Docker Sandbox support)
- **2 GB+ RAM** available for the Docker image build
- **API keys** in `~/.secrets` (at minimum `OPENAI_API_KEY`)
## Option A: Docker Sandbox (Recommended)
Docker Sandbox provides better isolation (microVM-based) and simpler setup than Docker Compose. Requires Docker Desktop v29+ / Docker Sandbox v0.12+.
```bash
# 1. Clone the OpenClaw repo and build the image
git clone https://github.com/openclaw/openclaw.git /tmp/openclaw-docker
cd /tmp/openclaw-docker
docker build -t openclaw:local -f Dockerfile .
# 2. Create the sandbox using the built image
docker sandbox create --name openclaw -t openclaw:local shell ~/.openclaw/workspace
# 3. Allow network access to OpenAI API
docker sandbox network proxy openclaw \
--allow-host api.openai.com \
--allow-host localhost
# 4. Write the config inside the sandbox
docker sandbox exec openclaw sh -c '
mkdir -p /home/node/.openclaw/workspace /home/node/.openclaw/identity /home/node/.openclaw/credentials
cat > /home/node/.openclaw/openclaw.json << INNEREOF
{
"gateway": {
"mode": "local",
"port": 18789,
"bind": "loopback",
"auth": {
"mode": "token",
"token": "sandbox-dev-token-12345"
},
"controlUi": { "enabled": true }
},
"agents": {
"defaults": {
"model": {
"primary": "openai/gpt-5.2",
"fallbacks": ["openai/gpt-5.2-chat-latest"]
},
"workspace": "/home/node/.openclaw/workspace"
}
}
}
INNEREOF
chmod 600 /home/node/.openclaw/openclaw.json
'
# 5. Start the gateway (pass your API key from ~/.secrets)
source ~/.secrets
docker sandbox exec -d \
-e OPENAI_API_KEY="$OPENAI_API_KEY" \
-w /app openclaw \
node dist/index.js gateway --bind loopback --port 18789
# 6. Wait ~15 seconds, then verify
sleep 15
docker sandbox exec openclaw curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:18789/
# Should print: 200
# 7. Check status
docker sandbox exec -e OPENAI_API_KEY="$OPENAI_API_KEY" -w /app openclaw \
node dist/index.js status
```
### Sandbox Management
```bash
# List sandboxes
docker sandbox ls
# Shell into the sandbox
docker sandbox exec -it openclaw bash
# Stop the sandbox (preserves state)
docker sandbox stop openclaw
# Remove the sandbox
docker sandbox rm openclaw
# Check sandbox version
docker sandbox version
```
## Option B: Docker Compose (Fallback)
Use this if Docker Sandbox is not available (Docker Desktop < v29).
```bash
# 1. Clone the OpenClaw repo
git clone https://github.com/openclaw/openclaw.git /tmp/openclaw-docker
cd /tmp/openclaw-docker
# 2. Build the Docker image (~5-10 min on first run)
docker build -t openclaw:local -f Dockerfile .
# 3. Create config directories
mkdir -p ~/.openclaw/workspace ~/.openclaw/identity ~/.openclaw/credentials
chmod 700 ~/.openclaw ~/.openclaw/credentials
# 4. Generate a gateway token
export OPENCLAW_GATEWAY_TOKEN=$(openssl rand -hex 32)
echo "Your gateway token: $OPENCLAW_GATEWAY_TOKEN"
# 5. Create the config file
cat > ~/.openclaw/openclaw.json << EOF
{
"gateway": {
"mode": "local",
"port": 18789,
"bind": "lan",
"auth": {
"mode": "token",
"token": "$OPENCLAW_GATEWAY_TOKEN"
},
"controlUi": {
"enabled": true,
"allowedOrigins": ["http://127.0.0.1:18789"]
}
},
"env": {
"OPENAI_API_KEY": "\${OPENAI_API_KEY}"
},
"agents": {
"defaults": {
"model": {
"primary": "openai/gpt-5.2",
"fallbacks": ["openai/gpt-5.2-chat-latest"]
},
"workspace": "/home/node/.openclaw/workspace"
}
}
}
EOF
chmod 600 ~/.openclaw/openclaw.json
# 6. Create the .env file (load API keys from ~/.secrets)
source ~/.secrets
cat > .env << EOF
OPENCLAW_CONFIG_DIR=$HOME/.openclaw
OPENCLAW_WORKSPACE_DIR=$HOME/.openclaw/workspace
OPENCLAW_GATEWAY_PORT=18789
OPENCLAW_BRIDGE_PORT=18790
OPENCLAW_GATEWAY_BIND=lan
OPENCLAW_GATEWAY_TOKEN=$OPENCLAW_GATEWAY_TOKEN
OPENCLAW_IMAGE=openclaw:local
OPENAI_API_KEY=$OPENAI_API_KEY
OPENCLAW_EXTRA_MOUNTS=
OPENCLAW_HOME_VOLUME=
OPENCLAW_DOCKER_APT_PACKAGES=
EOF
# 7. Add tmpfs to docker-compose.yml (required — see Known Issues)
# Add to BOTH openclaw-gateway and openclaw-cli services:
# tmpfs:
# - /tmp:exec,size=512M
# 8. Start the gateway
docker compose up -d openclaw-gateway
# 9. Wait ~15 seconds for startup, then get the dashboard URL
sleep 15
docker compose run --rm openclaw-cli dashboard --no-open
```
The dashboard URL will look like: `http://127.0.0.1:18789/#token=<your-token>`
### Docker Compose Management
```bash
cd /tmp/openclaw-docker
# Stop
docker compose down
# Start again (no rebuild needed)
docker compose up -d openclaw-gateway
# View logs
docker compose logs -f openclaw-gateway
# Check status
docker compose run --rm openclaw-cli status
# Get dashboard URL
docker compose run --rm openclaw-cli dashboard --no-open
```
## Known Issues and Fixes
### "no space left on device" when starting containers
Docker Desktop's virtual disk may be full.
```bash
docker system df # check usage
docker system prune -f # remove stopped containers, unused networks
docker image prune -f # remove dangling images
```
### "Unable to create fallback OpenClaw temp dir: /tmp/openclaw-1000" (Compose only)
The container can't write to `/tmp`. Add a `tmpfs` mount to `docker-compose.yml` for **both** services:
```yaml
services:
openclaw-gateway:
tmpfs:
- /tmp:exec,size=512M
openclaw-cli:
tmpfs:
- /tmp:exec,size=512M
```
This issue does not affect the Docker Sandbox approach.
### Node version mismatch in community template images
Some community-built sandbox templates (e.g. `olegselajev241/openclaw-dmr:latest`) ship Node 20, but OpenClaw requires Node >=22.12.0. Use our locally built `openclaw:local` image as the sandbox template instead, which includes Node 22.
### Gateway takes ~15 seconds to respond after start
The Node.js gateway needs time to initialize. Wait 15 seconds before hitting `http://127.0.0.1:18789/`.
### CLAUDE_AI_SESSION_KEY warnings (Compose only)
These Docker Compose warnings are harmless and can be ignored:
```
level=warning msg="The \"CLAUDE_AI_SESSION_KEY\" variable is not set. Defaulting to a blank string."
```
## Configuration
Config file: `~/.openclaw/openclaw.json` (JSON5 format)
Key settings:
- `gateway.auth.token` — the auth token for the web UI and API
- `agents.defaults.model.primary` — the AI model (use `openai/gpt-5.2` or newer)
- `env.OPENAI_API_KEY` — references the `OPENAI_API_KEY` env var (Compose approach)
API keys are stored in `~/.secrets` and passed into containers via env vars.
## Reference
- [OpenClaw Docker docs](https://docs.openclaw.ai/install/docker)
- [OpenClaw Configuration Reference](https://docs.openclaw.ai/gateway/configuration-reference)
- [Docker blog: Run OpenClaw Securely in Docker Sandboxes](https://www.docker.com/blog/run-openclaw-securely-in-docker-sandboxes/)
- [Docker Sandbox docs](https://docs.docker.com/ai/sandboxes)
- [OpenAI Models](https://platform.openai.com/docs/models) — current models: gpt-5.2, gpt-5.2-chat-latest, gpt-5.2-pro