47434a8ba7 fix: security hardening + real streaming (#4)
* Add graceful shutdown handling for SIGTERM and SIGINT

On receiving SIGTERM or SIGINT, the server now:
1. Stops accepting new connections (server.close)
2. Clears session cleanup and auth check intervals
3. Sends SIGTERM to all active child processes
4. Waits up to 5s for processes to exit, then SIGKILL + exit(1)
5. Exits cleanly with exit(0) once all processes are gone

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: security hardening — bind localhost, validate models, limit body size

- Bind to 127.0.0.1 instead of 0.0.0.0 to prevent network exposure
- Restrict CORS Access-Control-Allow-Origin to http://127.0.0.1
- Add 10MB request body size limit (413 on exceed)
- Validate model parameter against known MODEL_MAP keys (400 on unknown)
- Remove catch-all POST route; only /v1/chat/completions accepted
- Use crypto.timingSafeEqual for API key comparison
- Sanitize error messages to strip internal file paths

Bumps version to 2.4.1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: replace fake streaming with real stdout-piped SSE streaming

Previously, streaming requests waited for the entire claude CLI process
to complete, then split the output into artificial 500-char chunks sent
as SSE events. This defeated the purpose of streaming and added latency.

Now, stdout from the claude child process is piped directly to SSE
chunks as data arrives. Each `data` event from the process stdout
becomes an immediate SSE delta event, giving clients real incremental
output. The shared process setup logic is extracted into
spawnClaudeProcess() to avoid duplication between streaming and
non-streaming paths. Client disconnect detection kills the child process
to free resources.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: tighten CORS to dynamic localhost-only and reduce body limit to 5MB

- CORS now dynamically matches localhost/127.0.0.1 origins instead of
  static http://127.0.0.1, preventing cross-origin abuse while
  supporting any localhost port
- Body size limit reduced from 10MB to 5MB to limit OOM risk

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Oracle Public Cloud User <opc@instance-20230820-1333.subnet07301351.vcn07301351.oraclevcn.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 18:46:36 +10:00

openclaw-claude-proxy

Already paying for Claude Pro/Max? Use it as your OpenClaw model provider — $0 extra API cost.

A lightweight, zero-dependency proxy that lets OpenClaw agents talk to Claude through your existing subscription. One command to set up, one file to run.

v2.0.0 — Major Upgrade

What's new:

  • On-demand spawning — eliminates the pool crash loops, DEGRADED states, and stdin timeout errors from v1.x. Each request spawns a fresh claude -p process with stdin written immediately. No more stale workers, no more backoff spirals.
  • Session management — multi-turn conversations use --resume to avoid resending full history. Reduces token waste and enables Claude Code's built-in context compression on long conversations.
  • Full tool access — expanded default tools (Bash, Read, Write, Edit, Glob, Grep, WebSearch, WebFetch, Agent). Configurable via CLAUDE_ALLOWED_TOOLS or bypass all checks with CLAUDE_SKIP_PERMISSIONS=true.
  • System prompt pass-through — set CLAUDE_SYSTEM_PROMPT to inject context into every request.
  • MCP config support — set CLAUDE_MCP_CONFIG to load MCP servers (Telegram, etc.) into claude -p calls.
  • Concurrency controlCLAUDE_MAX_CONCURRENT prevents runaway process spawning (default: 5).
  • Auth health monitoring — periodic claude auth status checks with status exposed on /health.
  • Session APIGET /sessions to list, DELETE /sessions to clear active sessions.
  • Improved diagnostics/health endpoint shows stats, active sessions, recent errors, auth status, and full config.

Coexistence with Claude Code interactive mode: OCP and Claude Code (interactive/Telegram) run on completely different paths and can coexist on the same machine without conflict:

  • OCP: localhost:3456 (HTTP) → spawns claude -p processes (per-request, stateless)
  • CC: MCP protocol (in-process) → persistent interactive session
  • No shared ports, no shared processes, no shared sessions

Daemon advantage over CC: OCP runs as a system daemon (launchd/systemd) that auto-starts on boot and auto-recovers from crashes. Unlike Claude Code interactive mode, OCP does not require a terminal session to stay open — it survives disconnects, reboots, and SSH drops. Combined with OpenClaw's memory system, this means your agents never lose continuity.

How it works

OpenClaw Gateway → proxy (localhost:3456) → claude -p CLI → Anthropic (via OAuth)

The proxy translates OpenAI-compatible /v1/chat/completions requests into claude -p CLI calls. Anthropic sees normal Claude Code usage under your subscription — no API billing, no separate key.

Prerequisites

  • Node.js >= 18
  • Claude CLI installed and authenticated (claude login)
  • OpenClaw installed

Quick Start (Node.js)

git clone https://github.com/dtzp555-max/openclaw-claude-proxy.git
cd openclaw-claude-proxy

# Auto-configure OpenClaw + start proxy + install auto-start
node setup.mjs

That's it. The setup script will:

  1. Verify Claude CLI is installed and authenticated
  2. Add claude-local provider to openclaw.json
  3. Add auth profiles to all agents
  4. Start the proxy
  5. Install auto-start on login (launchd on macOS, systemd on Linux)

Then set your preferred Claude model as default:

openclaw config set agents.defaults.model.primary "claude-local/claude-opus-4-6"
openclaw gateway restart

Session Management (v2.0)

Multi-turn conversations can use sessions to avoid resending full message history on every request.

How to enable: Include a session_id or conversation_id field in your request body, or set the X-Session-Id / X-Conversation-Id header.

{
  "model": "claude-opus-4-6",
  "session_id": "conv-abc-123",
  "messages": [
    {"role": "user", "content": "Hello"},
    {"role": "assistant", "content": "Hi there!"},
    {"role": "user", "content": "What did I just say?"}
  ]
}

First request with a new session_id: all messages are sent, session is persisted via --session-id. Subsequent requests with the same session_id: only the latest user message is sent via --resume, reducing token consumption.

Sessions expire after 1 hour of inactivity (configurable via CLAUDE_SESSION_TTL).

API endpoints:

  • GET /sessions — list all active sessions
  • DELETE /sessions — clear all sessions

Security

  • Localhost only — the proxy binds to 127.0.0.1 and is not exposed to the internet or your local network
  • Bearer token auth (optional) — set PROXY_API_KEY to require a Bearer token on all requests (except /health). When unset, auth is disabled for backwards compatibility
  • No API keys for Claude — authentication to Anthropic goes through Claude CLI's OAuth session, no Anthropic credentials are stored in the proxy
  • Auto-start via launchd/systemdnode setup.mjs installs a user-level launch agent (macOS) or systemd user service (Linux) so the proxy starts automatically on login
  • Remove auto-start at any time:
node uninstall.mjs

Manual Install

1. Start the proxy

node server.mjs
# or in background:
bash start.sh

2. Configure OpenClaw

Add to ~/.openclaw/openclaw.json under models.providers:

"claude-local": {
  "baseUrl": "http://127.0.0.1:3456/v1",
  "api": "openai-completions",
  "apiKey": "<your PROXY_API_KEY, or omit if auth disabled>",
  "models": [
    {
      "id": "claude-opus-4-6",
      "name": "Claude Opus 4.6",
      "reasoning": true,
      "input": ["text"],
      "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
      "contextWindow": 200000,
      "maxTokens": 16384
    },
    {
      "id": "claude-sonnet-4-6",
      "name": "Claude Sonnet 4.6",
      "reasoning": true,
      "input": ["text"],
      "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
      "contextWindow": 200000,
      "maxTokens": 16384
    },
    {
      "id": "claude-haiku-4",
      "name": "Claude Haiku 4",
      "reasoning": false,
      "input": ["text"],
      "cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 },
      "contextWindow": 200000,
      "maxTokens": 8192
    }
  ]
}

3. Set as default model

openclaw config set agents.defaults.model.primary "claude-local/claude-opus-4-6"
openclaw gateway restart

Available Models

Model ID Claude CLI model Notes
claude-opus-4-6 claude-opus-4-6 Most capable, slower
claude-sonnet-4-6 claude-sonnet-4-6 Good balance of speed/quality
claude-haiku-4 claude-haiku-4-5-20251001 Fastest, lightweight

Environment Variables

Variable Default Description
CLAUDE_PROXY_PORT 3456 Listen port
CLAUDE_BIN (auto-detect) Path to claude binary
CLAUDE_TIMEOUT 300000 Request timeout (ms)
CLAUDE_ALLOWED_TOOLS Bash,Read,...,Agent Comma-separated tools to pre-approve
CLAUDE_SKIP_PERMISSIONS false Set true to bypass all permission checks
CLAUDE_SYSTEM_PROMPT (empty) System prompt appended to all requests
CLAUDE_MCP_CONFIG (empty) Path to MCP server config JSON file
CLAUDE_SESSION_TTL 3600000 Session expiry in ms (default: 1 hour)
CLAUDE_MAX_CONCURRENT 5 Max concurrent claude processes
PROXY_API_KEY (unset) Bearer token for API authentication

API Endpoints

  • GET /v1/models — List available models
  • POST /v1/chat/completions — Chat completion (streaming + non-streaming)
  • GET /health — Comprehensive health check (stats, sessions, auth, config)
  • GET /sessions — List active sessions
  • DELETE /sessions — Clear all sessions

Authentication

The proxy supports optional Bearer token authentication via the PROXY_API_KEY environment variable.

When PROXY_API_KEY is set, all requests (except GET /health) must include a valid Authorization: Bearer <token> header. Requests with a missing or invalid token receive a 401 Unauthorized response.

When PROXY_API_KEY is not set, authentication is disabled and all requests are accepted.

# Start with auth enabled
PROXY_API_KEY=my-secret-token node server.mjs

Architecture: v1 vs v2

v1.x (pool) v2.0 (on-demand)
Process lifecycle Pre-spawn idle workers Spawn per request
Crash handling Backoff → DEGRADED → manual restart No crash loops (no idle workers)
Session support None (stateless) --resume with session tracking
Tool access 6 tools hardcoded Configurable, expanded defaults
System prompt None CLAUDE_SYSTEM_PROMPT env
MCP support None CLAUDE_MCP_CONFIG env
Concurrency Unlimited (dangerous) CLAUDE_MAX_CONCURRENT limit
Auth monitoring None Periodic health checks
Diagnostics Basic /health Full stats, sessions, errors

Coexistence with Claude Code

OCP and Claude Code interactive mode (including Telegram bots) are completely independent:

OCP (this proxy) CC interactive
Protocol HTTP (localhost:3456) MCP (in-process)
Process model Per-request spawn Persistent session
Lifecycle Daemon (auto-start, auto-recover) Requires terminal
Permission model Pre-approved tools Interactive prompts
Use case Automated agent work Human-in-the-loop

Both can run on the same machine simultaneously. No shared state, no port conflicts.

Recovery after OpenClaw upgrade

OpenClaw upgrades (npm update -g openclaw) do not overwrite the user config at ~/.openclaw/openclaw.json. However, if the claude-local models stop working after an upgrade:

One-command recovery

cd ~/.openclaw/projects/claude-proxy   # or wherever you cloned it
git pull                                # pull latest version
node setup.mjs                          # reconfigure OpenClaw + start proxy
openclaw gateway restart

Notes

  • Cost shows as $0 because billing goes through your Claude subscription
  • Each request spawns a claude -p process; concurrent requests are capped by CLAUDE_MAX_CONCURRENT
  • The proxy must run on the same machine as the Claude CLI (uses local OAuth)
  • Session data is stored by Claude CLI on disk; session map is in-memory (lost on proxy restart)

License

MIT

S
Description
No description provided
Readme MIT
3.9 MiB
Languages
JavaScript 88.5%
Shell 9.8%
HTML 1.7%