- New server/config.mjs loads config.json, resolves secrets path relative to config dir, returns users/smtp/mail_perms_path/bind/port - server/secrets.mjs removed (logic absorbed into config.mjs) - smtp moves from secrets.json to config.json - secrets.json now contains only users (pure credentials) - config.example.json added as reference template - .gitignore/.npmignore updated to cover config.json and mail-perms.json - README updated with new setup and flags Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
212 lines
7.4 KiB
Markdown
212 lines
7.4 KiB
Markdown
# 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.
|
|
|
|
### Create a config file
|
|
|
|
Copy [`config.example.json`](config.example.json) to `config.json` and edit it:
|
|
|
|
```json
|
|
{
|
|
"secrets": "../secrets.json",
|
|
"mail_perms": "mail-perms.json",
|
|
"smtp": {
|
|
"host": "smtp.example.com",
|
|
"port": 587,
|
|
"secure": false,
|
|
"auth": { "user": "relay@example.com", "pass": "<password>" },
|
|
"from": "agent@example.com"
|
|
},
|
|
"bind": "127.0.0.1",
|
|
"port": 3015
|
|
}
|
|
```
|
|
|
|
`secrets` and `mail_perms` paths are resolved relative to the config file. `smtp`, `mail_perms`, `bind`, and `port` are all optional.
|
|
|
|
---
|
|
|
|
## Running
|
|
|
|
### Server (host)
|
|
|
|
```bash
|
|
ccc-server --config config.json
|
|
ccc-server --config config.json --dry-run
|
|
```
|
|
|
|
Server flags:
|
|
| Flag | Env variable | Description |
|
|
|------|-------------|-------------|
|
|
| `--config <path>` | `CONDUIT_CONFIG` | Path to config file (required) |
|
|
| `--dry-run` | — | Log all action invocations but do not execute them |
|
|
|
|
Server environment variables (override config file values):
|
|
| Variable | Description |
|
|
|----------|-------------|
|
|
| `CONDUIT_PORT` | Port to listen on |
|
|
| `CONDUIT_BIND` | Address to bind to |
|
|
| `CONDUIT_ROOT` | Label printed at startup (informational only — path resolution uses `VOLUME_MAPPING` in `server/helpers.mjs`) |
|
|
|
|
### 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, defaults to workspace) |
|
|
| `send-email` | auto-accept | `to`, `subject`, `body`, `topic` |
|
|
| `set-mail-permission` | auto-accept | `target_user`, `to`, `allow` (bool), `topic` (optional — omit to match any topic) |
|
|
| `get-mail-permissions` | auto-accept | `target_user` (optional) |
|
|
|
|
`send-email` checks that the caller has a mail permission entry matching `(caller, to, topic)` before sending. Permissions are managed via `set-mail-permission`, which requires the caller to have `canApprove` over the target user — so only humans can grant/revoke permissions for agents.
|
|
|
|
### 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 }, ctx) => {
|
|
// ctx = { caller, users, mail_perm_store, mailer_send }
|
|
// ctx is optional — omit the second argument if you don't need it
|
|
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` | `<CONTAINER_PATH>/workspace` |
|
|
| `/home/claude` | `<CONTAINER_PATH>/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
|