# claude-code-conduit A supervised action bridge between Claude Code and the host system. Claude requests structured actions; the server applies per-action policies and optionally holds them for human approval before executing. ## Concepts **Actions** are typed verbs with named parameters — not shell commands. The server defines what actions exist and what happens when they are called. Example: ```json { "action": "edit-file", "filename": "/workspace/foo.mjs" } ``` **Policies** control what happens when an action is requested: - `auto-accept` — executed immediately (e.g. open a file in the editor) - `auto-deny` — rejected immediately - `queue` — held for human approval (e.g. open a browser URL) **Authentication** uses HMAC-SHA256. Every request is signed with the caller's secret. Secrets live in a JSON file — never in environment variables. **Users** each have a secret and a `canApprove` list controlling whose queued actions they may approve. --- ## Setup ```bash npm install ``` ### Generate secrets ```bash # Create a secrets file with random secrets for each user ccc-keygen --create user,agent # Edit secrets.json to configure who can approve whom: # set user.canApprove = ["agent"] # Produce a filtered file for the agent (e.g. to copy into a Docker container) ccc-keygen --filter agent --output agent-secrets.json ``` The full `secrets.json` stays on the host. `agent-secrets.json` goes into the container. --- ## Running ### Server (host) ```bash ccc-server --secrets secrets.json ``` Environment variables: | Variable | Default | Description | |----------|---------|-------------| | `CONDUIT_PORT` | `3015` | Port to listen on | | `CONDUIT_ROOT` | `/workspace` | Workspace root for path resolution | ### Client (container / agent) ```bash ccc-client --secrets agent-secrets.json --user agent '{"action": "list-actions"}' ccc-client --secrets agent-secrets.json --user agent '{"action": "edit-file", "filename": "/workspace/foo.mjs"}' ``` `--secrets` and `--user` can also be set via environment variables: ```bash export CCC_SECRETS=/path/to/agent-secrets.json export CCC_USER=agent ccc-client '{"action": "list-actions"}' ``` The JSON payload can be spread across multiple arguments — they are space-joined before parsing: ```bash ccc-client '{"action": "edit-file",' '"filename": "/workspace/foo.mjs"}' ``` ### Queue manager (host) ```bash ccc-queue --secrets secrets.json --user user ``` Opens an interactive TUI showing pending actions: ``` ┌─ Pending Actions ──────────┐ ┌─ Details ───────────────────────────┐ │ │ │ │ │ > [a1b2c3] open-browser │ │ Action: open-browser │ │ [d4e5f6] open-terminal │ │ ID: a1b2c3d4-... │ │ │ │ Submitted by: agent │ │ │ │ Created: 2026-03-07T12:00:00Z │ │ │ │ │ │ │ │ Params: │ │ │ │ url: https://example.com │ └─────────────────────────────┘ └──────────────────────────────────────┘ [y] approve [n] deny [r] refresh [q] quit ``` Supports `CCC_SECRETS` and `CCC_USER` env vars the same as the client. --- ## Actions Query available actions at runtime: ```bash ccc-client '{"action": "list-actions"}' ``` Built-in actions: | Action | Policy | Params | |--------|--------|--------| | `list-actions` | auto-accept | — | | `edit-file` | auto-accept | `filename` (path) | | `open-browser` | queue | `url` (http/https only) | | `open-terminal` | queue | `path` (optional) | ### Adding actions Edit `server/actions.mjs`. Each entry needs: ```js 'my-action': { description: 'What this does', params: [{ name: 'foo', required: true, type: 'string' }], policy: 'auto-accept', // or 'auto-deny' | 'queue' handler: ({ foo }) => { // do something return { result: foo }; }, }, ``` --- ## Path resolution The server translates container-side paths to host-side paths using the volume map in `server/helpers.mjs`. By default this matches the `docker-compose.yml` layout: | Container path | Host path | |----------------|-----------| | `/workspace` | `/workspace` | | `/home/claude` | `/claude-home` | Paths outside known volumes are rejected. Edit `CONTAINER_PATH` and `VOLUME_MAPPING` in `server/helpers.mjs` to match your setup. --- ## Security notes - Secrets are never passed via environment variables or command line arguments — only via a file - HMAC signatures include a timestamp; requests older than 30 seconds are rejected - `canApprove` is empty by default — permissions must be explicitly granted - Browser URLs are validated to `http`/`https` only before being passed to `xdg-open` - All path arguments are resolved against the volume map; traversal outside known volumes is rejected