# 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. --- ## Install ```bash # Global install from Gitea npm install -g git+https://gitea.efforting.tech/mikael-lovqvists-claude-agent/claude-code-conduit.git # Or clone and link locally git clone git@git.efforting.tech:mikael-lovqvists-claude-agent/claude-code-conduit.git cd claude-code-conduit npm install npm link ``` ### 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 ``` Server environment variables: | Variable | Default | Description | |----------|---------|-------------| | `CONDUIT_PORT` | `3015` | Port to listen on | | `CONDUIT_BIND` | `127.0.0.1` | Address to bind to | | `CONDUIT_ROOT` | `/workspace` | Workspace root for path resolution | ### Client (container / agent) ```bash ccc-client --secrets agent-secrets.json --user agent --url http://192.168.2.99:3015 '{"action": "list-actions"}' ccc-client --secrets agent-secrets.json --user agent '{"action": "edit-file", "filename": "/workspace/foo.mjs"}' ``` `--secrets`, `--user`, and `--url` can also be set via environment variables: ```bash export CCC_SECRETS=/path/to/agent-secrets.json export CCC_USER=agent export CONDUIT_URL=http://192.168.2.99:3015 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 --url http://192.168.2.99:3015 ``` 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 ``` Client environment variables: | Variable | Default | Description | |----------|---------|-------------| | `CCC_SECRETS` | — | Path to secrets file | | `CCC_USER` | — | Username to authenticate as | | `CONDUIT_URL` | `http://localhost:3015` | Server URL | --- ## 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