{"id":50713469,"url":"https://github.com/alanzchen/clibroker","last_synced_at":"2026-06-09T17:02:44.499Z","repository":{"id":350108520,"uuid":"1200109471","full_name":"alanzchen/clibroker","owner":"alanzchen","description":"Policy-driven CLI command broker with REST, MCP, and direct client","archived":false,"fork":false,"pushed_at":"2026-04-09T00:26:20.000Z","size":63,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-09T00:27:14.776Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alanzchen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-03T03:38:02.000Z","updated_at":"2026-04-09T00:26:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/alanzchen/clibroker","commit_stats":null,"previous_names":["alanzchen/clibroker"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/alanzchen/clibroker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alanzchen%2Fclibroker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alanzchen%2Fclibroker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alanzchen%2Fclibroker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alanzchen%2Fclibroker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alanzchen","download_url":"https://codeload.github.com/alanzchen/clibroker/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alanzchen%2Fclibroker/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34116461,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-09T17:02:43.191Z","updated_at":"2026-06-09T17:02:44.487Z","avatar_url":"https://github.com/alanzchen.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# clibroker\n\n`clibroker` is a policy-driven broker for wrapping local CLI tools behind a secure HTTP API and MCP server.\n\nIt has two surfaces:\n\n- a server that wraps and executes approved CLI commands\n- a client that talks to that server, fetches token-scoped configuration, and forwards execution requests\n\nIt is designed for cases where you want an LLM or another client to use a CLI tool, but only within a tightly-defined allowlist.\n\n## What It Does\n\n- Runs as a broker server with `clibroker`\n- Ships a direct client with `clibroker-client`\n- Exposes a single REST endpoint: `POST /execute`\n- Exposes a token-scoped client discovery endpoint: `GET /client-config`\n- Exposes MCP tools derived from allowed policy rules\n- Exposes configured per-tool host directories through authenticated file sharing\n- Enforces deny-by-default policy evaluation\n- Validates flags and positional arguments before execution\n- Applies per-token RBAC\n- Executes subprocesses without invoking a shell\n- Isolates subprocess environments unless explicit env vars are configured\n- Caps output and enforces timeouts\n- Emits structured JSON audit logs\n\n## Security Model\n\n- No shell: commands are executed with `asyncio.create_subprocess_exec()`\n- Deny by default: if no allow rule matches, the request is rejected\n- Deny precedence: deny rules override allows, including child command paths\n- RBAC: each bearer token is allowed to invoke only specific rule IDs\n- MCP isolation: each token gets its own MCP server view with only authorized tools visible\n- Secret-safe MCP URLs: MCP/SSE endpoints use `SHA-256(token)[:16]` slugs instead of raw tokens\n- File sharing: host paths stay server-side, URLs require bearer auth, and every path is contained under the configured share root\n\n## Requirements\n\n- Python `\u003e=3.11`\n\n## Install\n\nPrefer `uv` for Python environment and package installation.\n\nFor system-wide CLI installation, prefer `uv tool`.\n\n### System-Wide CLI Install\n\nFrom the public GitHub repository:\n\nServer command only:\n\n```bash\nuv tool install 'git+https://github.com/alanzchen/clibroker'\n```\n\nServer + client commands:\n\n```bash\nuv tool install 'clibroker[client] @ git+https://github.com/alanzchen/clibroker'\n```\n\nThis installs the published CLI application into an isolated tool environment and exposes:\n\n- `clibroker`\n- `clibroker-client`\n\n### Local Project Install\n\nFor local development, editable installs, or working from a checkout, use `uv venv` + `uv pip`.\n\nServer only:\n\n```bash\nuv venv .venv\nuv pip install --python .venv/bin/python -e .\n```\n\nServer + client support:\n\n```bash\nuv venv .venv\nuv pip install --python .venv/bin/python -e .[client]\n```\n\nDevelopment:\n\n```bash\nuv venv .venv\nuv pip install --python .venv/bin/python -e .[dev]\n```\n\nInstalled commands:\n\n- `clibroker`: start the broker server\n- `clibroker-client`: connect to a broker server\n\n## Configuration\n\nThe server and client use separate YAML configs.\n\n### Server Config\n\nStart from `config.example.yaml`:\n\n```bash\ncp config.example.yaml config.yaml\n```\n\nMain sections:\n\n- `server.bind`: host and port to listen on\n- `server.auth.tokens`: bearer tokens and their allowed rule IDs\n- `tools.\u003cname\u003e.executable`: absolute path to the wrapped CLI\n- `tools.\u003cname\u003e.default_args`: always prepended to the command\n- `tools.\u003cname\u003e.env`: explicit subprocess environment variables\n- `tools.\u003cname\u003e.file_sharing`: host directories exposed for authenticated file access\n- `tools.\u003cname\u003e.rules`: allow/deny policy rules\n\nExample token config:\n\n```yaml\nserver:\n  auth:\n    tokens:\n      - name: reader\n        value: \"env:CLIBROKER_TOKEN_READER\"\n        allow_rules:\n          - list_messages\n```\n\nToken values may be literal strings or `env:VAR_NAME` references.\n\nExample file sharing config:\n\n```yaml\ntools:\n  himalaya:\n    working_dir: /srv/clibroker/himalaya\n    file_sharing:\n      expose_working_dir: true\n      max_file_bytes: 1048576\n      shares:\n        - name: attachments\n          path: /srv/clibroker/attachments\n          access: read_write\n```\n\nFile sharing behavior:\n\n- absolute `working_dir` values are exposed as a read-only share named `working_dir` by default\n- explicit shares support `access: read` or `access: read_write`\n- a token can access a tool's file shares when it has at least one allow-rule for that tool\n- host paths are never exposed through `/client-config`, MCP tool results, or file URLs\n- file paths must stay under the share root; absolute paths, `..`, backslashes, NUL bytes, and symlink escapes are rejected\n\n### Client Config\n\nStart from `client.example.yaml`:\n\n```bash\ncp client.example.yaml client.yaml\n```\n\nExample:\n\n```yaml\ndefault_backend: local\n\nbackends:\n  local:\n    type: http\n    base_url: http://127.0.0.1:8080\n    token: env:CLIBROKER_TOKEN_READER\n    timeout_s: 30.0\n    verify_tls: true\n  review:\n    type: http\n    base_url: http://127.0.0.1:8081\n    token: env:CLIBROKER_TOKEN_REVIEW\n    timeout_s: 30.0\n    verify_tls: true\n```\n\nCurrent backend types:\n\n- `http`: direct HTTPS/HTTP connection to the broker server\n\nClient tokens also support `env:VAR_NAME` references.\n\n## Running\n\n### Server\n\n```bash\n.venv/bin/clibroker --config config.yaml\n```\n\nDevelopment mode with reload:\n\n```bash\n.venv/bin/clibroker --config config.yaml --reload\n```\n\n### Client\n\nList tools visible to the configured token:\n\n```bash\n.venv/bin/clibroker-client --config client.yaml tools\n```\n\nThe client also supports config discovery in this order:\n\n- `--config`\n- `CLIBROKER_CLIENT_CONFIG`\n- `~/.openclaw/clibroker-client.yaml`\n- `${XDG_CONFIG_HOME:-~/.config}/clibroker/client.yaml`\n\nSo if your config is already in one of those default locations, you can simply run:\n\n```bash\n.venv/bin/clibroker-client tools\n```\n\nSelect a non-default server backend with `--backend`:\n\n```bash\n.venv/bin/clibroker-client --backend review tools\n```\n\nIf you do not pass `--backend`, the client behaves like this:\n\n- if only one backend is configured, it uses that backend\n- if multiple backends are configured and a tool name exists in exactly one backend, `execute` auto-selects that backend\n- if the same tool name exists in multiple backends, `execute` fails and tells you to rerun with `--backend \u003cname\u003e`\n\nWith multiple configured backends, `tools --json` returns an aggregate view that includes a `tool_index` showing which backends expose each tool and whether the tool name is conflicted.\n\nForward an execute request to the server:\n\n```bash\n.venv/bin/clibroker-client --config client.yaml execute himalaya -- message read 42\n```\n\nShow the selected local backend config with secrets redacted:\n\n```bash\n.venv/bin/clibroker-client --config client.yaml config show\n```\n\nList all configured backends:\n\n```bash\n.venv/bin/clibroker-client config list\n```\n\n## HTTP API\n\n### Health Check\n\n```bash\ncurl http://127.0.0.1:8080/health\n```\n\nResponse:\n\n```json\n{\"status\":\"ok\",\"version\":\"0.1.0\"}\n```\n\n### Execute a Command\n\n```bash\ncurl -X POST http://127.0.0.1:8080/execute \\\n  -H 'Authorization: Bearer YOUR_TOKEN' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"tool\": \"himalaya\",\n    \"argv\": [\"message\", \"read\", \"42\"]\n  }'\n```\n\nRequest body:\n\n```json\n{\n  \"tool\": \"himalaya\",\n  \"argv\": [\"message\", \"move\", \"42\", \"Archive\"]\n}\n```\n\nResponse shape:\n\n```json\n{\n  \"ok\": true,\n  \"exit_code\": 0,\n  \"stdout\": {},\n  \"stderr\": \"\",\n  \"duration_ms\": 12.34,\n  \"matched_rule\": \"move_message\",\n  \"timed_out\": false\n}\n```\n\nNotes:\n\n- `argv` must contain at least one element\n- `stdout` is parsed as JSON when possible; otherwise it is returned as a string\n- policy denials and validation failures return `200` with `ok: false`\n- auth failures return `401` or `403`\n\n### File Sharing\n\nConfigured shares are available under authenticated `/files` URLs:\n\n```bash\ncurl http://127.0.0.1:8080/files/himalaya/attachments/report.pdf \\\n  -H 'Authorization: Bearer YOUR_TOKEN' \\\n  -o report.pdf\n```\n\nDirectory requests return JSON listings:\n\n```bash\ncurl http://127.0.0.1:8080/files/himalaya/attachments \\\n  -H 'Authorization: Bearer YOUR_TOKEN'\n```\n\nFile sharing notes:\n\n- `GET /files/\u003ctool\u003e/\u003cshare\u003e/\u003cpath\u003e` requires the standard bearer header\n- files are returned as downloads; directories return JSON entries\n- directory listings include `truncated` and `max_entries` fields when the listing cap is reached\n- generated file URLs are relative and do not contain secrets\n- read-write shares are writable through MCP file tools, not through the HTTP API\n\n### Client Discovery\n\nThe broker client fetches a token-scoped discovery document from the server.\n\n```bash\ncurl http://127.0.0.1:8080/client-config \\\n  -H 'Authorization: Bearer YOUR_TOKEN'\n```\n\nExample response:\n\n```json\n{\n  \"version\": \"0.1.0\",\n  \"client_name\": \"reader\",\n  \"execute_url\": \"/execute\",\n  \"token_info_url\": \"/token-info\",\n  \"mcp_url\": \"/mcp/0123456789abcdef/\",\n  \"sse_url\": \"/sse/0123456789abcdef/\",\n  \"tools\": [\n    {\n      \"name\": \"himalaya\",\n      \"rules\": [\n        {\n          \"id\": \"list_messages\",\n          \"command\": [\"message\", \"list\"],\n          \"flags\": [\"--account\", \"--folder\", \"--page\"],\n          \"standalone_flags\": [\"--unread\"],\n          \"positionals\": []\n        }\n      ],\n      \"file_shares\": [\n        {\n          \"name\": \"working_dir\",\n          \"access\": \"read\",\n          \"url\": \"/files/himalaya/working_dir\"\n        },\n        {\n          \"name\": \"attachments\",\n          \"access\": \"read_write\",\n          \"url\": \"/files/himalaya/attachments\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nThis response is token-scoped:\n\n- only allow-rules for the authenticated token are returned\n- only file shares for tools authorized by the authenticated token are returned\n- deny rules are omitted\n- raw server config and secrets are not returned\n\n## MCP\n\n`clibroker` exposes both streamable HTTP MCP and SSE MCP transports.\n\nEndpoints:\n\n- `POST /mcp/\u003cslug\u003e/`\n- `GET /sse/\u003cslug\u003e/`\n\nWhere:\n\n- `slug = SHA-256(token)[:16]`\n\nTo discover your slug:\n\n```bash\ncurl http://127.0.0.1:8080/token-info \\\n  -H 'Authorization: Bearer YOUR_TOKEN'\n```\n\nExample response:\n\n```json\n{\n  \"name\": \"reader\",\n  \"slug\": \"0123456789abcdef\",\n  \"mcp_url\": \"/mcp/0123456789abcdef/\",\n  \"sse_url\": \"/sse/0123456789abcdef/\",\n  \"allow_rules\": [\"list_messages\", \"read_message\"]\n}\n```\n\nMCP behavior:\n\n- each token only sees the tools for its allowed rule IDs\n- deny rules do not appear in MCP `tools/list`\n- MCP tool calls still pass through the policy engine before execution\n- file shares appear as `\u003ctool\u003e__files_*` MCP tools for authorized tools\n- read-write shares support MCP create, write, move, and delete operations\n\n## Client CLI\n\nThe `clibroker-client` command does not execute local subprocesses. It talks to the server using the configured backend and lets the server remain the security boundary.\n\nCurrent commands:\n\n- `tools`: fetch and print the token-scoped discovery document\n- `execute \u003ctool\u003e -- \u003cargv...\u003e`: forward an execution request to the server\n- `config show`: show the selected local client backend config with secrets redacted\n\nExamples:\n\n```bash\n.venv/bin/clibroker-client --config client.yaml tools --json\n.venv/bin/clibroker-client --config client.yaml execute himalaya -- message list --account work\n.venv/bin/clibroker-client --config client.yaml config show\n```\n\n## Policy Rules\n\nEach rule includes:\n\n- `id`: unique rule ID\n- `command`: command path, such as `['message', 'read']`\n- `effect`: `allow` or `deny`\n- `flags.allowed`: allowed flags that require a value\n- `flags.standalone`: allowed boolean flags that take no value\n- `inject_args`: fixed server-side args always inserted for the rule\n- `positionals`: positional argument validators\n\nExample allow rule:\n\n```yaml\n- id: read_message\n  command: [\"message\", \"read\"]\n  effect: allow\n  inject_args: [\"--preview\"]\n  flags:\n    allowed: [\"--account\", \"--folder\"]\n  positionals:\n    - name: id\n      pattern: \"^[0-9]+$\"\n```\n\nExample variadic tail rule:\n\n```yaml\n- id: search_messages\n  command: [\"envelope\", \"list\"]\n  effect: allow\n  flags:\n    allowed: [\"--account\", \"--folder\", \"--page\", \"--page-size\"]\n  positionals:\n    - name: query\n      pattern: \"^[A-Za-z0-9_@.+:-]+$\"\n      variadic: true\n```\n\nExample deny rule:\n\n```yaml\n- id: deny_delete\n  command: [\"message\", \"delete\"]\n  effect: deny\n```\n\nImportant validation rules:\n\n- `command` must contain at least one element\n- unknown flags are rejected\n- `--flag=value` is supported\n- `--` marks end-of-options\n- `flags.allowed` entries must consume one value argument\n- `flags.standalone` entries must not consume a value\n- `flags.allowed` and `flags.standalone` must be disjoint\n- only the final positional may be marked `variadic: true`\n- a variadic positional validates each token in the tail individually\n- deny rules cascade to child command paths\n\nNotes:\n\n- `inject_args` are server-controlled and are not exposed as client-supplied parameters in `/client-config` or MCP tool schemas\n- execution order is `executable + default_args + command + inject_args + validated user args`\n\n## Testing\n\nRun all tests:\n\n```bash\n.venv/bin/python -m pytest tests -v\n```\n\nThe current suite covers REST, MCP, file sharing, policy evaluation, subprocess hardening, and security fixes.\n\n## Project Layout\n\n```text\nsrc/clibroker/\n  app.py         FastAPI app factory\n  auth.py        Bearer auth and RBAC\n  client/        Client package and CLI\n  config.py      YAML/Pydantic config models\n  file_sharing.py Safe per-tool host directory sharing\n  mcp_server.py  MCP server and tool registration\n  middleware.py  Request timeout middleware\n  models.py      REST request/response models\n  policy.py      Command matching and argv validation\n  routes.py      /execute route\n  runner.py      Hardened subprocess execution\n  audit.py       Structured JSON audit logging\n```\n\n## Known Limits\n\n- no rate limiting yet\n- no graceful child-process shutdown on app stop yet\n- regex patterns come directly from config, so pattern quality matters\n- the client currently supports only the direct HTTP backend\n- the client CLI does not have dedicated file commands; use MCP file tools or authenticated `/files/...` URLs\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falanzchen%2Fclibroker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falanzchen%2Fclibroker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falanzchen%2Fclibroker/lists"}