openclaw_oauth_sync/README.md
sol 2db7d7d90a feat: merge Gitea webhook security into setup wizard (issue #2)
Integrates the 5-layer Gitea webhook security system from
sol/clawgravity-hook-security (v2.0) into the setup wizard.

## What's added

### New files (from clawgravity-hook-security v2.0)
- scripts/webhook-security/gitea-hmac-verify.js    -- njs HMAC-SHA256 module
- scripts/webhook-security/gitea-approve-repo       -- allowlist helper
- scripts/webhook-security/rotate-webhook-secret.sh -- monthly secret rotation (templated)
- scripts/webhook-security/webhook-audit-alert.sh   -- daily audit summaries (templated)
- scripts/webhook-security/ntfy-blocked-pickup.sh   -- blocked webhook alerts (templated)
- templates/webhook-security/nginx-site.conf.example
- templates/webhook-security/nginx.conf.example
- templates/webhook-security/gitea-repo-allowlist.json.example
- docs/WEBHOOK-SECURITY.md   -- full documentation
- docs/SECURITY-AUDIT.md     -- 35-case test matrix
- tests/test-webhook-security.sh  -- 48 offline tests

### Modified files
- setup.sh: Step 11 (webhook security wizard with 6 sub-sections)
- scripts/uninstall.sh: webhook security cleanup section
- README.md: Webhook Security section after Quick Start
- Makefile: test target now runs test-webhook-security.sh
- .secret-scan-allowlist: allowlist docs/SECURITY-AUDIT.md (test fixture)

## Security layers
1. IP allowlisting (nginx)
2. Rate limiting 10 req/s burst 20 (nginx)
3. Payload size 1MB (nginx)
4. HMAC-SHA256 signature verification (njs)
5. Per-repository allowlist (njs)

## make check
- prettier: PASS
- secret-scan: PASS
- tests: 48/48 PASS

Closes #2
2026-03-01 08:43:02 +00:00

308 lines
11 KiB
Markdown

# OAuth Fix for OpenClaw + Claude Max
> Created by **ROOH** — [<project-url>](https://<project-url>)
Automatic Anthropic OAuth token refresh for OpenClaw. Keeps your Claude Max tokens alive indefinitely.
## The Problem
OpenClaw uses Anthropic Claude models (e.g., `claude-opus-4-6`) via OAuth tokens from Claude Max subscriptions. These tokens **expire every ~8 hours**. Without automated refresh, all agents stop working with:
```
HTTP 401 authentication_error: OAuth token has expired
```
## The Solution
Two services working together:
1. **Auto-refresh trigger** — A timer that runs Claude CLI every 30 minutes to check and refresh the token before it expires. Supports Claude CLI in a Docker container or installed on the host.
2. **Token sync watcher** — An `inotifywait` service that detects when Claude CLI writes new credentials and instantly syncs them to OpenClaw.
```
Timer (every 30min) Claude Code CLI sync-oauth-token.sh OpenClaw Gateway
(triggers CLI when ---> (refreshes token, ---> (watches for changes, ---> (gets fresh token)
token near expiry) writes creds file) syncs via inotifywait)
```
## Quick Start
```bash
git clone <repository-url>
cd openclaw_oauth_sync
sudo ./setup.sh
```
The interactive wizard will:
1. Check prerequisites (offers to install python3, curl, inotify-tools if missing)
2. Detect your OpenClaw installation paths
3. Find Claude CLI credentials (offers to install CLI and help with sign-in if needed)
4. Configure the Anthropic model (if not already set up)
5. Install the token sync watcher (inotifywait or timer fallback)
6. Detect Claude CLI (container or host) and install the auto-refresh trigger
7. Test the CLI invocation to confirm it works
8. Verify everything works
Every install step asks for your confirmation first — you have full control over what gets installed.
## Webhook Security (Optional)
The setup wizard includes an optional Step 11 that installs 5-layer webhook
security for the `/hooks/gitea` endpoint. This protects your OpenClaw instance
from unauthorized webhook requests.
**Security layers:**
1. IP allowlisting (only your Gitea server can send webhooks)
2. Rate limiting (10 req/s, burst 20)
3. Payload size limit (1MB)
4. HMAC-SHA256 signature verification (njs module)
5. Per-repository allowlist (only approved repos can trigger agents)
**What gets installed (when you choose to enable it during setup):**
- nginx njs HMAC module (`/etc/nginx/njs/gitea-hmac-verify.js`)
- Webhook secret file (`/etc/nginx/gitea-webhook-secret`)
- Repository allowlist (`/etc/nginx/gitea-repo-allowlist.json`)
- Helper scripts (`/opt/webhook-security/scripts/`)
- `gitea-approve-repo` command (add repos to allowlist)
After installation, manage the allowlist with:
```bash
gitea-approve-repo owner/repo # Allow a specific repo
cat /etc/nginx/gitea-repo-allowlist.json # View current allowlist
```
Full documentation: [docs/WEBHOOK-SECURITY.md](docs/WEBHOOK-SECURITY.md)
Security audit: [docs/SECURITY-AUDIT.md](docs/SECURITY-AUDIT.md)
## Prerequisites
- Linux server with **systemd**
- **Docker** + **Docker Compose v2**
- **OpenClaw** installed and running
- **Claude Max subscription** with OAuth credentials
- **Claude Code CLI** — either in a Docker container or installed on the host (wizard can install it)
- **python3** (wizard offers to install if missing)
- **curl** (wizard offers to install if missing)
- **inotify-tools** (optional, wizard offers to install if missing)
## How It Works
### Architecture
```
+---------------------------+
| trigger-claude-refresh.sh | (systemd timer, every 30 min)
| checks token expiry |
| if < 1.5h remaining: |
| triggers Claude CLI |
+-------------+-------------+
|
v
+--------------------+ auto-refresh +-------------------+
| Claude Code CLI | =================> | .credentials.json |
| (container or host)| (token near expiry) +--------+----------+
+--------------------+ |
inotifywait detects change
|
+----------v----------+
| sync-oauth-token.sh |
+--+------+------+----+
| | |
oauth.json .env gateway
(mapped (env restart
fields) var) (down/up)
```
### Token Flow
1. `trigger-claude-refresh.sh` runs every 30 minutes, checks token expiry
2. If token has < 1.5 hours remaining, triggers Claude CLI
3. Claude CLI detects its token is near expiry
4. CLI calls Anthropic's refresh endpoint, gets new access token
5. CLI writes updated `.credentials.json`
6. `inotifywait` detects the file change (< 1 second)
7. `sync-oauth-token.sh` reads the new token
8. Maps fields: `accessToken` -> `access`, `refreshToken` -> `refresh`, `expiresAt` -> `expires`
9. Writes to `oauth.json` (OpenClaw's format)
10. Updates `ANTHROPIC_OAUTH_TOKEN` in `.env`
11. Recreates gateway container (`docker compose down/up` — NOT restart!)
12. Gateway starts with the fresh token
### ANTHROPIC_BASE_URL Override
If Claude CLI runs in a container with `ANTHROPIC_BASE_URL` set to a proxy (e.g., LiteLLM), the trigger script uses a **temporary per-invocation override**:
```bash
docker exec -e ANTHROPIC_BASE_URL=https://api.anthropic.com container claude -p "say ok"
```
The `-e` flag overrides the env var only for that single command. The container's running processes are unaffected. This is detected and configured automatically by the wizard.
### Re-authentication Notification
If the refresh token itself expires (e.g., subscription lapsed), the trigger script:
- Creates a flag file at `REAUTH_NEEDED` in the OpenClaw directory
- Logs an error to journalctl
- Future: Mattermost webhook notification
### Why down/up and NOT restart?
`docker compose restart` does **NOT** reload `.env` variables. It restarts the same container with the same environment. Only `docker compose down` + `docker compose up -d` creates a new container that reads `.env` fresh.
## Anthropic Model Configuration
OpenClaw has a **built-in** Anthropic provider. **Do NOT** add `anthropic` to `models.providers` in `openclaw.json` — it causes double `/v1` in URLs resulting in 404 errors.
The correct configuration (set by the wizard):
```json
{
"agents": {
"defaults": {
"model": {
"primary": "anthropic/claude-opus-4-6"
},
"models": {
"anthropic/claude-opus-4-6": { "alias": "Claude Opus 4.6 (Max)" },
"anthropic/claude-sonnet-4-6": { "alias": "Claude Sonnet 4.6 (Max)" }
}
}
}
}
```
See [docs/OPENCLAW-MODEL-CONFIG.md](docs/OPENCLAW-MODEL-CONFIG.md) for full details.
## Credential Field Mapping
Claude CLI and OpenClaw use different field names:
| Claude CLI (`.credentials.json`) | OpenClaw (`oauth.json`) |
| -------------------------------- | ----------------------- |
| `claudeAiOauth.accessToken` | `anthropic.access` |
| `claudeAiOauth.refreshToken` | `anthropic.refresh` |
| `claudeAiOauth.expiresAt` | `anthropic.expires` |
See [docs/FIELD-MAPPING.md](docs/FIELD-MAPPING.md) for all formats.
## Manual Installation
If you prefer not to use the wizard:
### 1. Install inotify-tools
```bash
apt install inotify-tools
```
### 2. Edit and install the sync script
```bash
# Edit scripts/sync-oauth-token.sh — replace @@PLACEHOLDER@@ values:
# @@CLAUDE_CREDS_FILE@@ = path to Claude CLI .credentials.json
# @@OPENCLAW_OAUTH_FILE@@ = path to OpenClaw oauth.json
# @@OPENCLAW_ENV_FILE@@ = path to OpenClaw .env
# @@COMPOSE_DIR@@ = path to OpenClaw docker-compose directory
cp scripts/sync-oauth-token.sh /usr/local/bin/
chmod +x /usr/local/bin/sync-oauth-token.sh
```
### 3. Install systemd service
```bash
# Edit templates/sync-oauth-token.service — replace @@SYNC_SCRIPT_PATH@@
cp templates/sync-oauth-token.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now sync-oauth-token.service
```
### 4. Install the auto-refresh trigger
```bash
# Edit scripts/trigger-claude-refresh.sh — replace @@PLACEHOLDER@@ values:
# @@CREDS_FILE@@ = path to Claude CLI .credentials.json
# @@REAUTH_FLAG@@ = path to REAUTH_NEEDED flag file
# @@CLI_MODE@@ = "container" or "host"
# @@CLI_CONTAINER@@ = container name (if container mode)
# @@CLI_BASE_URL_OVERRIDE@@ = "true" or "false"
cp scripts/trigger-claude-refresh.sh /usr/local/bin/
chmod +x /usr/local/bin/trigger-claude-refresh.sh
# Edit templates/trigger-claude-refresh.service — replace @@TRIGGER_SCRIPT_PATH@@
cp templates/trigger-claude-refresh.service /etc/systemd/system/
cp templates/trigger-claude-refresh.timer /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now trigger-claude-refresh.timer
```
### 5. Configure OpenClaw
See [docs/OPENCLAW-MODEL-CONFIG.md](docs/OPENCLAW-MODEL-CONFIG.md).
## Verification
```bash
# Run the health check
./scripts/verify.sh
# Watch sync logs in real-time
journalctl -u sync-oauth-token.service -f
# Check trigger logs
journalctl -u trigger-claude-refresh -n 20
# Check all timers
systemctl list-timers sync-oauth-token* trigger-claude-refresh*
# Check service status
systemctl status sync-oauth-token.service
systemctl status trigger-claude-refresh.timer
```
## Uninstall
```bash
./setup.sh --uninstall
# or
./scripts/uninstall.sh
```
## Fallback Method (Timer)
If `inotifywait` is unavailable, the wizard installs a systemd timer that refreshes the token directly via Anthropic's API every 6 hours. This is less responsive but doesn't require inotify.
## Troubleshooting
See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) for common issues:
- Token expired errors
- `docker compose restart` not reloading env
- Auth profile `key` vs `access` field
- 404 from custom anthropic provider
- Cooldown errors
- Claude CLI not responding (ANTHROPIC_BASE_URL override)
- REAUTH_NEEDED flag (refresh token expired)
## Documentation
- [Architecture](docs/ARCHITECTURE.md) — Token flow, volume mounts, auth resolution
- [Troubleshooting](docs/TROUBLESHOOTING.md) — Common issues and fixes
- [Model Config](docs/OPENCLAW-MODEL-CONFIG.md) — Anthropic model setup in OpenClaw
- [Token Refresh](docs/HOW-TOKEN-REFRESH-WORKS.md) — How Claude CLI refreshes tokens
- [Field Mapping](docs/FIELD-MAPPING.md) — Credential format reference
## Author
**ROOH** — [<project-url>](https://<project-url>)
## License
MIT