@cldmv/gh-broker 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -40
- package/config.example.json +21 -8
- package/config.sample.json +45 -0
- package/deploy/README.md +73 -15
- package/deploy/install.mjs +166 -101
- package/deploy/install.sh +0 -0
- package/deploy/lib/config-merge.mjs +174 -16
- package/deploy/lib/credentials.mjs +143 -0
- package/deploy/lib/install-args.mjs +144 -0
- package/deploy/lib/npm-setup.mjs +164 -0
- package/deploy/lib/secret-store.mjs +20 -2
- package/deploy/lib/system.mjs +26 -2
- package/dist/index.mjs +1638 -202
- package/dist/index.mjs.map +4 -4
- package/docs/TOOLS.md +192 -1
- package/docs/changelog/v0/v0.8.0.md +115 -0
- package/docs/deploy-architecture.md +13 -11
- package/package.json +7 -7
- package/src/availability.ts +42 -0
- package/src/catalog.ts +18 -1
- package/src/config.ts +29 -2
- package/src/router.ts +43 -2
- package/src/secrets.ts +34 -4
- package/src/types.ts +25 -1
- package/src/verbs/ghcr/archive.ts +314 -0
- package/src/verbs/ghcr/config.ts +142 -0
- package/src/verbs/ghcr/jobs.ts +141 -0
- package/src/verbs/ghcr/push.ts +192 -0
- package/src/verbs/ghcr/registry.ts +275 -0
- package/src/verbs/ghcr/tar.ts +203 -0
- package/src/verbs/ghcr_push.ts +264 -0
- package/src/verbs/npm-registry.ts +168 -0
- package/src/verbs/npm-tarball.ts +76 -0
- package/src/verbs/npm_scaffold.ts +325 -0
- package/src/verbs/release-analysis.ts +3 -1
- package/tests/catalog.test.vitest.mts +55 -1
- package/tests/config-merge.test.vitest.mts +275 -8
- package/tests/config.test.vitest.mts +75 -1
- package/tests/credentials.test.vitest.mts +127 -0
- package/tests/ghcr-archive.test.vitest.mts +439 -0
- package/tests/ghcr-config.test.vitest.mts +96 -0
- package/tests/ghcr-push.test.vitest.mts +526 -0
- package/tests/ghcr-registry.test.vitest.mts +206 -0
- package/tests/helpers/ghcr-fixtures.mts +373 -0
- package/tests/install-args.test.vitest.mts +152 -0
- package/tests/issue-labels.test.vitest.mts +15 -0
- package/tests/npm-scaffold.test.vitest.mts +479 -0
- package/tests/npm-setup.test.vitest.mts +196 -0
- package/tests/paths.test.vitest.mts +5 -0
- package/tests/prompt.test.vitest.mts +128 -0
- package/tests/release-prs.test.vitest.mts +10 -0
- package/tests/router.test.vitest.mts +108 -2
- package/tests/secret-store.test.vitest.mts +64 -3
- package/tests/secrets.test.vitest.mts +53 -0
- package/tests/security-alerts.test.vitest.mts +10 -0
- package/tests/server.test.vitest.mts +187 -7
- package/tests/smoke.test.vitest.mts +177 -0
- package/tests/system.test.vitest.mts +355 -0
- package/tests/verb-helpers.test.vitest.mts +46 -0
- package/.configs/fix-headers.json +0 -3
- package/.configs/vitest.config.mjs +0 -44
- package/.github/dependabot.yml +0 -111
- package/.github/workflows/branch-retention.yml +0 -44
- package/.github/workflows/bundle-size.yml +0 -52
- package/.github/workflows/ci.yml +0 -389
- package/.github/workflows/cla.yml +0 -66
- package/.github/workflows/codeql.yml +0 -87
- package/.github/workflows/dependabot-auto-merge.yml +0 -62
- package/.github/workflows/dependabot-recreate.yml +0 -67
- package/.github/workflows/dependency-review.yml +0 -46
- package/.github/workflows/feature-pr.yml +0 -75
- package/.github/workflows/hotfix-redirector.yml +0 -67
- package/.github/workflows/hotfixes-release.yml +0 -70
- package/.github/workflows/labeler.yml +0 -48
- package/.github/workflows/master-commit-audit.yml +0 -56
- package/.github/workflows/member-auto-merge.yml +0 -75
- package/.github/workflows/next-release.yml +0 -76
- package/.github/workflows/next-reset.yml +0 -50
- package/.github/workflows/pr-notify.yml +0 -44
- package/.github/workflows/pr-title-normalizer.yml +0 -52
- package/.github/workflows/provenance.yml +0 -51
- package/.github/workflows/publish.yml +0 -146
- package/.github/workflows/release-merge.yml +0 -104
- package/.github/workflows/release-notify.yml +0 -41
- package/.github/workflows/scorecard.yml +0 -64
- package/.github/workflows/stale.yml +0 -50
- package/.github/workflows/tag-health.yml +0 -70
- package/.github/workflows/update-major-version-tags.yml +0 -94
- package/.github/workflows/v4-bootstrap.yml +0 -115
- package/.github/workflows/welcome.yml +0 -42
package/README.md
CHANGED
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
# @cldmv/gh-broker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**@cldmv/gh-broker** is a local **credential-routing MCP proxy** for GitHub. It lets an autonomous agent read and write issues, pull requests and more across several orgs **without ever holding a token**: the agent gets to *act*, never to *possess* the credential.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The hosted GitHub MCP authenticates with one token per server, so several orgs means several servers, and the token sits in a config file the agent can read. A classic PAT spans orgs but is coarse; a GitHub App acts as a bot and can't drive the MCP; `gh` and raw PATs are readable by the agent (`gh auth token`). gh-broker covers what none of them do together: the user's own identity, least privilege, several orgs, one server, and a secret the agent cannot exfiltrate.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
It runs as a dedicated `gh-broker` system user, holds a read-scoped and a write-scoped fine-grained PAT per org in an agent-unreadable secret store (`systemd-creds` or a `0640` file), and picks the right one per call by repo owner and read/write mode. The toolset is config-driven and default-deny: mostly proxied to GitHub's hosted MCP, plus custom verbs for what the hosted MCP has no tool for, and augmented tools that extend a proxied tool's result. The agent reaches it over a permissioned Unix socket that carries MCP frames and never credential material.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
> _The agent gets to act, never to hold the credential._
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
[![npm version]][npm_version_url] [![npm downloads]][npm_downloads_url] [![GitHub downloads]][github_downloads_url] [![Last commit]][last_commit_url] [![npm last update]][npm_last_update_url] [![Coverage]][coverage_url]
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- exposes a **config-driven, default-deny** toolset — mostly **proxied** to GitHub's hosted MCP (which already does labels, type, issue fields, dedup), plus **custom verbs** for gaps the hosted MCP has no tool for, and **augmented** tools that extend a proxied tool's response in place. See [Key Features](#-key-features) and [docs/TOOLS.md](docs/TOOLS.md) for the full set;
|
|
16
|
-
- talks to the agent over a **Unix socket** (bytes only; no token crosses).
|
|
13
|
+
> [!NOTE]
|
|
14
|
+
> **Status: 0.x, in production.** Running under systemd and brokering live GitHub reads and writes. Linux and systemd only for now ([#9](https://github.com/CLDMV/gh-broker/issues/9)). `link_issue` (App-token only) is implemented but not wired in, see [deploy/README.md](deploy/README.md#enabling-link_issue-github-app--deferred).
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ✨ What's New
|
|
21
|
+
|
|
22
|
+
### Latest: v0.8.0 (October 2026)
|
|
23
|
+
|
|
24
|
+
- **`npm_scaffold`: new npm packages without a manual first publish.** The new write verb publishes a broker-built `0.0.0-stage` placeholder and registers the repo's publish workflow as its trusted publisher, so CI publishes the first real release through OIDC. The npm token has no 2FA bypass: each write takes a one-time password from the user, so it's two calls, and the verb refuses any package that already exists.
|
|
25
|
+
- **`ghcr_push`: container image pushes without a registry token.** Push a `docker save` archive from a drop directory to an allowlisted `ghcr.io/<owner>/<image>` as a background job, follow it with `ghcr_push_status` and check it with `ghcr_tags`. Release tags are never overwritten, and the classic PAT stays with the broker.
|
|
26
|
+
- **Optional credentials.** The npm and GHCR tokens can be skipped at install; their tools stay hidden until the token is stored. The installer also takes tokens via `--secret <cred-name>=<token>`.
|
|
27
|
+
- [View full v0.8.0 Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.8.0.md)
|
|
28
|
+
|
|
29
|
+
### Recent Releases
|
|
30
|
+
|
|
31
|
+
- **v0.7.0** (October 2026) — `issue_labels` adds, removes or swaps single labels without replacing the set, and `issue_write` warns that its `labels` does ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.7.0.md))
|
|
32
|
+
- **v0.6.1** (October 2026) — MCP SDK 1.32.0 (same-origin redirects only) and a refreshed vitest toolchain; no broker source changes ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.6.1.md))
|
|
33
|
+
- **v0.6.0** (October 2026) — Compact per-repo roll-up, filters and flow tier for `report_security_alerts`, and release-readiness analysis for `report_release_prs` ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.6.0.md))
|
|
34
|
+
- **v0.5.1** (October 2026) — The installer keeps the existing secrets backend instead of prompting on every upgrade ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.5.1.md))
|
|
35
|
+
|
|
36
|
+
📚 For the complete version history, see the [docs/changelog/](https://github.com/CLDMV/gh-broker/tree/master/docs/changelog/) folder.
|
|
21
37
|
|
|
22
38
|
---
|
|
23
39
|
|
|
@@ -59,6 +75,16 @@ The hosted MCP's `issue_write` takes `labels` as a full replacement, so changing
|
|
|
59
75
|
|
|
60
76
|
🏷️ **Full parameter reference: [docs/TOOLS.md](docs/TOOLS.md#issue_labels)**
|
|
61
77
|
|
|
78
|
+
### 📦 **New npm Packages Without a Manual First Publish**
|
|
79
|
+
|
|
80
|
+
npm only accepts a trusted publisher on a package that already exists, so every new package used to need a hand-run first publish and a form on npmjs.com. `npm_scaffold` does both: it publishes a broker-built placeholder (`0.0.0-stage`, no code) and registers the repo's publish workflow as the trusted publisher, after which CI publishes every release through OIDC. The broker's npm token has no 2FA bypass, so every npm write needs a one-time password from the user: one call publishes, a second call with the next code adds the trusted publisher. The verb refuses any package that already exists.
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{ "owner": "cldmv", "repo": "new-thing", "package": "@cldmv/new-thing", "otp": "123456" }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
📦 **Full parameter reference: [docs/TOOLS.md](docs/TOOLS.md#npm_scaffold)**
|
|
87
|
+
|
|
62
88
|
### 🛡️ **Org-Wide Security Alerts**
|
|
63
89
|
|
|
64
90
|
`report_security_alerts` lists every open Dependabot, CodeQL and secret-scanning alert — for one repo or across everything an owner has — using the org-level endpoints where the token can read them and a per-repo sweep where it can't. Secret values are never returned.
|
|
@@ -71,6 +97,16 @@ The hosted MCP's `issue_write` takes `labels` as a full replacement, so changing
|
|
|
71
97
|
|
|
72
98
|
🚢 **Full parameter reference: [docs/TOOLS.md](docs/TOOLS.md#report_release_prs)**
|
|
73
99
|
|
|
100
|
+
### 🐳 **Container Image Pushes Without a Registry Token**
|
|
101
|
+
|
|
102
|
+
`ghcr_push` pushes a `docker save` archive to `ghcr.io/<owner>/<image>:<tag>` with a classic `write:packages` PAT the agent never sees. The image must be on an allowlist, the archive must sit in a dedicated drop directory (symlinks, hard links and paths outside it are refused), and an existing release tag is never overwritten. The push runs as a background job, uploads only the blobs the registry doesn't already have, and is tracked with `ghcr_push_status`; `ghcr_tags` checks the result.
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{ "owner": "cldmv", "image": "hoot", "tag": "1.4.0", "archive": "hoot-1.4.0.tar" }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
🐳 **Full parameter reference and threat model: [docs/TOOLS.md](docs/TOOLS.md#ghcr_push)**
|
|
109
|
+
|
|
74
110
|
### 🔧 **Extensible By Design**
|
|
75
111
|
|
|
76
112
|
The broker presents its **own** MCP contract to the agent — a proxied tool's schema and response are not required to match the hosted MCP's exactly. Adding a custom verb or extending a proxied tool with new parameters follows a documented pattern, not a one-off hack.
|
|
@@ -85,29 +121,32 @@ The broker presents its **own** MCP contract to the agent — a proxied tool's s
|
|
|
85
121
|
|
|
86
122
|
---
|
|
87
123
|
|
|
88
|
-
##
|
|
124
|
+
## 📦 Installation
|
|
89
125
|
|
|
90
|
-
###
|
|
126
|
+
### Requirements
|
|
91
127
|
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
- **
|
|
95
|
-
-
|
|
128
|
+
- **Linux with systemd.** The broker runs as a hardened systemd service and listens on a Unix socket.
|
|
129
|
+
- **Node.js 22.12.0 or later** (`engines.node` is `>=22.12.0`). The installer points `/usr/local/bin/node` at the node it runs under.
|
|
130
|
+
- **The agent runs as its own Linux user**, without passwordless sudo, so it can't read the broker's secrets. The model behind that is in [docs/agent-user-model.md](docs/agent-user-model.md).
|
|
131
|
+
- **Two fine-grained PATs per org** (read and write). The scopes are listed in [deploy/README.md](deploy/README.md).
|
|
96
132
|
|
|
97
|
-
###
|
|
133
|
+
### Install
|
|
98
134
|
|
|
99
|
-
-
|
|
100
|
-
- **v0.6.0** (October 2026) — Compact per-repo roll-up, filters and flow tier for `report_security_alerts`, and release-readiness analysis for `report_release_prs` ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.6.0.md))
|
|
101
|
-
- **v0.5.1** (October 2026) — The installer keeps the existing secrets backend instead of prompting on every upgrade ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.5.1.md))
|
|
102
|
-
- **v0.5.0** (October 2026) — New `report_security_alerts` and `report_release_prs` verbs, and a permission 403 is no longer mistaken for a rate limit ([Changelog](https://github.com/CLDMV/gh-broker/blob/master/docs/changelog/v0/v0.5.0.md))
|
|
135
|
+
gh-broker is published on npm as [`@cldmv/gh-broker`](https://www.npmjs.com/package/@cldmv/gh-broker), but it is a system service, not a library or a global CLI. Install it from a checkout with the installer, which handles both the first install and every upgrade:
|
|
103
136
|
|
|
104
|
-
|
|
137
|
+
```sh
|
|
138
|
+
git clone https://github.com/CLDMV/gh-broker.git
|
|
139
|
+
cd gh-broker
|
|
140
|
+
sudo node deploy/install.mjs <agent-user>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The installer creates the service user and the socket group, builds as the checkout's owner, merges the shipped defaults with the deployed config, stores the PATs in the backend you pick, installs the unit and runs a smoke test. Prerequisites, PAT scopes, unattended installs and every flag: [deploy/README.md](deploy/README.md).
|
|
105
144
|
|
|
106
145
|
---
|
|
107
146
|
|
|
108
|
-
##
|
|
147
|
+
## 🚀 Quick Start
|
|
109
148
|
|
|
110
|
-
|
|
149
|
+
Point the agent's MCP client at the socket ([deploy/README.md § 3](deploy/README.md#3-point-claude-at-the-broker)). From then on the client sees an ordinary `tools/call`, and the broker resolves the credential, routes it, and (for custom/augmented tools) does the extra work in between:
|
|
111
150
|
|
|
112
151
|
```json
|
|
113
152
|
// tools/call
|
|
@@ -123,7 +162,7 @@ No token ever appears in the request, the response, or anywhere the agent can re
|
|
|
123
162
|
|
|
124
163
|
---
|
|
125
164
|
|
|
126
|
-
## Layout
|
|
165
|
+
## 📁 Layout
|
|
127
166
|
|
|
128
167
|
```
|
|
129
168
|
src/
|
|
@@ -143,12 +182,17 @@ src/
|
|
|
143
182
|
report_ci_status.ts tiered CI status for a PR or every open PR in a repo
|
|
144
183
|
rest-client.ts shared rate-limit-floor + call-budget REST helpers
|
|
145
184
|
issue_labels.ts add/remove/swap/set labels on an issue or PR (write PAT)
|
|
185
|
+
npm_scaffold.ts create an npm package + its trusted publisher (npm token + user OTP)
|
|
186
|
+
npm-registry.ts the npm registry requests npm_scaffold makes
|
|
187
|
+
npm-tarball.ts minimal npm tarball builder for the placeholder
|
|
146
188
|
review_thread.ts resolve/reply PR review threads (write PAT)
|
|
189
|
+
ghcr_push.ts ghcr_push / ghcr_push_status / ghcr_tags (GHCR classic PAT)
|
|
190
|
+
ghcr/ config policy, tar + image-archive reader, OCI registry client, push, job queue
|
|
147
191
|
link_issue.ts addClosingIssueReference (App token) — DEFERRED, not wired in
|
|
148
192
|
deploy/
|
|
149
193
|
install.mjs interactive installer — first install AND upgrade (sudo node)
|
|
150
194
|
install.sh optional Linux bootstrap — set up node, then hand off to install.mjs
|
|
151
|
-
lib/ installer pieces (config merge, secret store, system ops, smoke test)
|
|
195
|
+
lib/ installer pieces (config merge, secret store, system ops, smoke test, npm token setup)
|
|
152
196
|
gh-broker.service systemd unit (hardened)
|
|
153
197
|
README.md prerequisites + PAT scopes + install/upgrade/wiring
|
|
154
198
|
docs/
|
|
@@ -156,31 +200,81 @@ docs/
|
|
|
156
200
|
deploy-architecture.md config merge, secret store, node handling, installer flow
|
|
157
201
|
agent-user-model.md the agent-as-separate-user isolation model (example setup)
|
|
158
202
|
changelog/ per-version release notes
|
|
159
|
-
config.example.json shipped defaults (
|
|
203
|
+
config.example.json shipped generic defaults (socket, upstream, tools allowlist)
|
|
204
|
+
config.sample.json a full config with placeholder values, for reference only
|
|
160
205
|
```
|
|
161
206
|
|
|
207
|
+
---
|
|
208
|
+
|
|
162
209
|
## 📚 Documentation
|
|
163
210
|
|
|
164
|
-
- **[Tools Reference](docs/TOOLS.md)** — every custom and augmented tool: `report_unlabeled`, `report_security_alerts`, `report_release_prs`, `report_ci_status`, the `pull_request_read` `ciVerbosity` extension, `issue_labels`, `resolve_review_thread`/`reply_review_thread`, and how to add a new one
|
|
211
|
+
- **[Tools Reference](docs/TOOLS.md)** — every custom and augmented tool: `report_unlabeled`, `report_security_alerts`, `report_release_prs`, `report_ci_status`, the `pull_request_read` `ciVerbosity` extension, `issue_labels`, `npm_scaffold`, `resolve_review_thread`/`reply_review_thread`, `ghcr_push`/`ghcr_push_status`/`ghcr_tags`, and how to add a new one
|
|
165
212
|
- **[Agent-as-Separate-User Model](docs/agent-user-model.md)** — the isolation model behind "use ≠ possession": users, groups, filesystem permissions, verified against a real host
|
|
166
213
|
- **[Deploy Architecture](docs/deploy-architecture.md)** — config merge, secret store backends, node handling, the installer's full flow
|
|
167
214
|
- **[Install Guide](deploy/README.md)** — prerequisites, PAT scopes, install/upgrade command, flags
|
|
168
215
|
- **[Changelog](docs/changelog/)** — per-version release notes
|
|
169
216
|
|
|
170
|
-
|
|
217
|
+
[![CodeFactor]][codefactor_url] [![OpenSSF Scorecard]][ossf_scorecard_url] [![npms.io score]][npms_url] [![npm unpacked size]][npm_size_url] [![Repo size]][repo_size_url]
|
|
171
218
|
|
|
172
|
-
|
|
219
|
+
---
|
|
173
220
|
|
|
174
|
-
##
|
|
221
|
+
## 🤝 Contributing
|
|
175
222
|
|
|
176
|
-
|
|
177
|
-
npm ci
|
|
178
|
-
npm run typecheck
|
|
179
|
-
npm run build
|
|
180
|
-
```
|
|
223
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/CLDMV/gh-broker/issues). Pull requests target the `next` branch; releases ship from `next` to `master`. Run `npm run build:ci` and `npm run ci:coverage` before opening one; coverage is held at 100%.
|
|
181
224
|
|
|
182
|
-
|
|
225
|
+
[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]
|
|
183
226
|
|
|
184
|
-
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 🔗 Links
|
|
230
|
+
|
|
231
|
+
- **npm**: [@cldmv/gh-broker](https://www.npmjs.com/package/@cldmv/gh-broker)
|
|
232
|
+
- **GitHub**: [CLDMV/gh-broker](https://github.com/CLDMV/gh-broker)
|
|
233
|
+
- **Issues**: [GitHub Issues](https://github.com/CLDMV/gh-broker/issues)
|
|
234
|
+
- **Changelog**: [docs/changelog/](https://github.com/CLDMV/gh-broker/tree/master/docs/changelog/)
|
|
235
|
+
- **Releases**: [GitHub Releases](https://github.com/CLDMV/gh-broker/releases)
|
|
236
|
+
|
|
237
|
+
---
|
|
185
238
|
|
|
186
|
-
|
|
239
|
+
## 📄 License
|
|
240
|
+
|
|
241
|
+
[![GitHub license]][github_license_url] [![npm license]][npm_license_url]
|
|
242
|
+
|
|
243
|
+
Apache-2.0 © Shinrai / CLDMV. See [LICENSE](https://github.com/CLDMV/gh-broker/blob/master/LICENSE) for the full text.
|
|
244
|
+
|
|
245
|
+
<!-- Badge definitions -->
|
|
246
|
+
<!-- [github release]: https://img.shields.io/github/v/release/CLDMV/gh-broker?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 -->
|
|
247
|
+
<!-- [github_release_url]: https://github.com/CLDMV/gh-broker/releases -->
|
|
248
|
+
<!-- [release date]: https://img.shields.io/github/release-date/CLDMV/gh-broker?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 -->
|
|
249
|
+
<!-- [release_date_url]: https://github.com/CLDMV/gh-broker/releases -->
|
|
250
|
+
|
|
251
|
+
[npm version]: https://img.shields.io/npm/v/%40cldmv%2Fgh-broker.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
|
|
252
|
+
[npm_version_url]: https://www.npmjs.com/package/@cldmv/gh-broker
|
|
253
|
+
[npm downloads]: https://img.shields.io/npm/dm/%40cldmv%2Fgh-broker.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
|
|
254
|
+
[npm_downloads_url]: https://www.npmjs.com/package/@cldmv/gh-broker
|
|
255
|
+
[github downloads]: https://img.shields.io/github/downloads/CLDMV/gh-broker/total?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
|
|
256
|
+
[github_downloads_url]: https://github.com/CLDMV/gh-broker/releases
|
|
257
|
+
[last commit]: https://img.shields.io/github/last-commit/CLDMV/gh-broker?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
|
|
258
|
+
[last_commit_url]: https://github.com/CLDMV/gh-broker/commits
|
|
259
|
+
[npm last update]: https://img.shields.io/npm/last-update/%40cldmv%2Fgh-broker?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
|
|
260
|
+
[npm_last_update_url]: https://www.npmjs.com/package/@cldmv/gh-broker
|
|
261
|
+
[coverage]: https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FCLDMV%2Fgh-broker%2Fbadges%2Fcoverage.json&style=for-the-badge&logo=vitest&logoColor=white
|
|
262
|
+
[coverage_url]: https://github.com/CLDMV/gh-broker/blob/badges/coverage.json
|
|
263
|
+
[contributors]: https://img.shields.io/github/contributors/CLDMV/gh-broker.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
|
|
264
|
+
[contributors_url]: https://github.com/CLDMV/gh-broker/graphs/contributors
|
|
265
|
+
[sponsor shinrai]: https://img.shields.io/github/sponsors/shinrai?style=for-the-badge&logo=githubsponsors&logoColor=white&labelColor=EA4AAA&label=Sponsor
|
|
266
|
+
[sponsor_url]: https://github.com/sponsors/shinrai
|
|
267
|
+
[codefactor]: https://img.shields.io/codefactor/grade/github/CLDMV/gh-broker?style=for-the-badge&logo=codefactor&logoColor=white&labelColor=F44A6A
|
|
268
|
+
[codefactor_url]: https://www.codefactor.io/repository/github/cldmv/gh-broker
|
|
269
|
+
[openssf scorecard]: https://img.shields.io/ossf-scorecard/github.com/CLDMV/gh-broker?style=for-the-badge&label=OpenSSF%20Scorecard
|
|
270
|
+
[ossf_scorecard_url]: https://scorecard.dev/viewer/?uri=github.com/CLDMV/gh-broker
|
|
271
|
+
[npms.io score]: https://img.shields.io/npms-io/final-score/%40cldmv%2Fgh-broker?style=for-the-badge&logo=npms&logoColor=white&labelColor=0B5D57
|
|
272
|
+
[npms_url]: https://npms.io/search?q=%40cldmv%2Fgh-broker
|
|
273
|
+
[npm unpacked size]: https://img.shields.io/npm/unpacked-size/%40cldmv%2Fgh-broker.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
|
|
274
|
+
[npm_size_url]: https://www.npmjs.com/package/@cldmv/gh-broker
|
|
275
|
+
[repo size]: https://img.shields.io/github/repo-size/CLDMV/gh-broker?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
|
|
276
|
+
[repo_size_url]: https://github.com/CLDMV/gh-broker
|
|
277
|
+
[npm license]: https://img.shields.io/npm/l/%40cldmv%2Fgh-broker.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
|
|
278
|
+
[npm_license_url]: https://www.npmjs.com/package/@cldmv/gh-broker
|
|
279
|
+
[github license]: https://img.shields.io/github/license/CLDMV/gh-broker.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
|
|
280
|
+
[github_license_url]: https://github.com/CLDMV/gh-broker/blob/master/LICENSE
|
package/config.example.json
CHANGED
|
@@ -240,6 +240,11 @@
|
|
|
240
240
|
"mode": "write",
|
|
241
241
|
"custom": true
|
|
242
242
|
},
|
|
243
|
+
{
|
|
244
|
+
"name": "npm_scaffold",
|
|
245
|
+
"mode": "write",
|
|
246
|
+
"custom": true
|
|
247
|
+
},
|
|
243
248
|
{
|
|
244
249
|
"name": "resolve_review_thread",
|
|
245
250
|
"mode": "write",
|
|
@@ -269,13 +274,21 @@
|
|
|
269
274
|
"name": "report_ci_status",
|
|
270
275
|
"mode": "read",
|
|
271
276
|
"custom": true
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"name": "ghcr_push",
|
|
280
|
+
"mode": "write",
|
|
281
|
+
"custom": true
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"name": "ghcr_push_status",
|
|
285
|
+
"mode": "read",
|
|
286
|
+
"custom": true
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"name": "ghcr_tags",
|
|
290
|
+
"mode": "read",
|
|
291
|
+
"custom": true
|
|
272
292
|
}
|
|
273
|
-
]
|
|
274
|
-
"owners": {
|
|
275
|
-
"cldmv": {
|
|
276
|
-
"read": "cred:cldmv-read",
|
|
277
|
-
"write": "cred:cldmv-write"
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
"defaultOwner": "cldmv"
|
|
293
|
+
]
|
|
281
294
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"listen": "unix:/run/gh-broker/gh-broker.sock",
|
|
3
|
+
"upstream": "https://api.githubcopilot.com/mcp/x/all",
|
|
4
|
+
"defaultOwner": "your-org",
|
|
5
|
+
"owners": {
|
|
6
|
+
"your-org": {
|
|
7
|
+
"read": "cred:your-org-read",
|
|
8
|
+
"write": "cred:your-org-write",
|
|
9
|
+
"installationId": 12345678
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"app": {
|
|
13
|
+
"appId": 123456,
|
|
14
|
+
"privateKey": "cred:gh-app-key"
|
|
15
|
+
},
|
|
16
|
+
"secrets": {
|
|
17
|
+
"provider": "file",
|
|
18
|
+
"path": "/etc/gh-broker/secrets.json"
|
|
19
|
+
},
|
|
20
|
+
"npm": {
|
|
21
|
+
"token": "cred:npm-token",
|
|
22
|
+
"scopes": [
|
|
23
|
+
"@your-org"
|
|
24
|
+
],
|
|
25
|
+
"registry": "https://registry.npmjs.org",
|
|
26
|
+
"tokenOrgs": [
|
|
27
|
+
"your-org"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"ghcr": {
|
|
31
|
+
"dropDir": "/srv/gh-broker/ghcr-drop",
|
|
32
|
+
"protectReleaseTags": true,
|
|
33
|
+
"maxArchiveBytes": 17179869184,
|
|
34
|
+
"owners": {
|
|
35
|
+
"your-org": {
|
|
36
|
+
"username": "your-login",
|
|
37
|
+
"credential": "cred:your-org-packages",
|
|
38
|
+
"images": [
|
|
39
|
+
"your-image",
|
|
40
|
+
"your-image-worker-*"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
package/deploy/README.md
CHANGED
|
@@ -9,15 +9,21 @@ One interactive installer does first-install and upgrade. Design and rationale:
|
|
|
9
9
|
|
|
10
10
|
The installer maintains `/usr/local/bin/node` as the single node indirection (CLI and service resolve the same link). Bumping node is re-pointing that symlink — which the installer does on every run.
|
|
11
11
|
|
|
12
|
-
## 1. Mint the two
|
|
12
|
+
## 1. Mint the two fine-grained PATs
|
|
13
13
|
|
|
14
|
-
GitHub → Settings → Developer settings → **Fine-grained tokens** → Generate new token, **twice**. Resource owner:
|
|
14
|
+
For each GitHub owner (user or org) the broker serves, `<org>`: GitHub → Settings → Developer settings → **Fine-grained tokens** → Generate new token, **twice**. Resource owner: `<org>`. Repository access: **All repositories**.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
**`<org>-read`** — Issues: Read, Pull requests: Read, Contents: Read, Metadata: Read (auto).
|
|
17
|
+
**`<org>-write`** — Issues: R/W, Pull requests: R/W, Contents: R/W, Metadata: Read (auto).
|
|
18
18
|
|
|
19
19
|
No organization or admin permissions. If the org requires approval for fine-grained PATs, approve them in Org → Settings → Personal access tokens. The two-PAT split (read vs write) is a deliberate least-privilege control — see the design doc.
|
|
20
20
|
|
|
21
|
+
### GHCR pushes: one classic PAT (`<org>-packages`)
|
|
22
|
+
|
|
23
|
+
`ghcr_push` needs a third credential, because the GitHub Container Registry does not accept fine-grained PATs. Create a **classic** token with **`write:packages` only** (it implies `read:packages`; do not add `delete:packages`, and add `repo` only if pushes to a private-repo-linked package are refused without it), and set an expiry: [create the token](https://github.com/settings/tokens/new?scopes=write:packages&description=gh-broker%20ghcr). Enable it with `--ghcr-owner <org> --ghcr-username <login> --ghcr-image <name>` (or a `ghcr` block in a `--config` file, see [Configuring the install](#configuring-the-install)). The installer then prompts for the token as `<org>-packages`; it is an **optional credential**, so you can press Enter to skip it (an unattended run skips it with a note), and the broker then starts normally with the `ghcr_*` tools hidden until the token is stored. Supply it on a later run with `--secret <org>-packages=<token>`, `--secrets-file` or env `GH_BROKER_SECRET_<ORG>_PACKAGES`. `--ghcr-username` (`ghcr.owners.<org>.username`) must be the GitHub login that owns the token.
|
|
24
|
+
|
|
25
|
+
The installer also creates the drop directory from `ghcr.dropDir` (default `/srv/gh-broker/ghcr-drop`) as `2750 <agent-user>:gh-broker`: the agent writes archives there, the setgid bit gives each file the broker's group, and nobody else can look in. Archives must be group-readable (`chmod g+r`); the broker can only read them, so the agent deletes them after a push. See [`../docs/TOOLS.md`](../docs/TOOLS.md#ghcr_push).
|
|
26
|
+
|
|
21
27
|
## 2. Install (first install AND upgrade — same command)
|
|
22
28
|
|
|
23
29
|
```sh
|
|
@@ -28,26 +34,27 @@ sudo node deploy/install.mjs [agent-user]
|
|
|
28
34
|
|
|
29
35
|
1. checks prerequisites,
|
|
30
36
|
2. **creates or confirms the agent user** (stops if it can't — no silent half-install),
|
|
31
|
-
3. creates the `gh-broker` service user + `gh-broker-clients` bridge group, adds the agent user to it,
|
|
37
|
+
3. creates the `gh-broker` service user + `gh-broker-clients` bridge group, adds the agent user to it (and, when the config has a `ghcr` block, creates the GHCR drop directory),
|
|
32
38
|
4. points `/usr/local/bin/node` at the running node,
|
|
33
39
|
5. builds `dist/` if it isn't already built, or rebuilds it if a prior run left it (or `node_modules/`) owned by someone other than the checkout's owner — the build itself runs via `sudo -u '#<uid>'` as whoever owns `deploy/install.mjs`, not as root, so the installer never leaves root-owned files behind in your checkout,
|
|
34
|
-
6. regenerates the config by
|
|
35
|
-
7.
|
|
36
|
-
8.
|
|
37
|
-
9.
|
|
38
|
-
10.
|
|
40
|
+
6. regenerates the config by layering `config.example.json`, whatever is deployed, your `--config` file and your flags (your data is preserved; allowlist changes from the repo arrive; see [Configuring the install](#configuring-the-install)); a first install with no owner asks for one,
|
|
41
|
+
7. sets up the optional **npm integration** when asked (offered on a first install, or `--npm`): an npm web login it waits on, then the token it creates for `npm_scaffold` (see [npm integration](#npm-integration-npm_scaffold)),
|
|
42
|
+
8. **asks how to store the credentials** — `systemd-creds` (encrypted at rest, host-bound; recommended) or a plaintext `file` (`0640 root:gh-broker`; portable) — then stores the tokens in the chosen backend: it keeps existing creds, decrypts a legacy `systemd-creds` token when you switch to `file`, and **prompts only for what it can't find** (input hidden),
|
|
43
|
+
9. validates the config with the real loader (`--check`),
|
|
44
|
+
10. installs the unit, deploys `dist/`, restarts, and
|
|
45
|
+
11. runs a **smoke test** (MCP handshake + a real read over the socket) → ✅/❌.
|
|
39
46
|
|
|
40
47
|
### Unattended (single non-interactive command)
|
|
41
48
|
|
|
42
|
-
Pass flags plus `--non-interactive` (`-y`) to skip every prompt: `--agent-user <name>`, `--secrets-provider systemd-creds|file`, `--create-agent-user`, and supply tokens via `--secrets-file <json>` (`{ "<cred-name>": "<token>" }`)
|
|
49
|
+
Pass flags plus `--non-interactive` (`-y`) to skip every prompt: `--agent-user <name>`, `--secrets-provider systemd-creds|file`, `--create-agent-user`, and supply tokens via `--secrets-file <json>` (`{ "<cred-name>": "<token>" }`), env `GH_BROKER_SECRET_<NAME>` (NAME upper-cased, non-alnum → `_`), or `--secret <cred-name>=<token>` (repeatable; a name the config doesn't use is refused, so a typo can't silently do nothing; it is visible in the process list and shell history, so prefer the file or env). Precedence is `--secret`, then the file, then env, then the interactive prompt; a token from any of the first three replaces a stored one. Example:
|
|
43
50
|
|
|
44
51
|
```sh
|
|
45
|
-
sudo
|
|
46
|
-
/usr/local/bin/node deploy/install.mjs --agent-user repo \
|
|
52
|
+
sudo GH_BROKER_SECRET_ACME_READ=… GH_BROKER_SECRET_ACME_WRITE=… \
|
|
53
|
+
/usr/local/bin/node deploy/install.mjs --agent-user repo --owner acme \
|
|
47
54
|
--secrets-provider systemd-creds --non-interactive
|
|
48
55
|
```
|
|
49
56
|
|
|
50
|
-
Migrating an existing `systemd-creds` install needs **no tokens** — `--secrets-provider systemd-creds --non-interactive` keeps the existing `.cred` files (nothing to prompt). `--help` lists every flag.
|
|
57
|
+
Re-running on an existing install needs no `--owner`: the deployed owners are kept. Migrating an existing `systemd-creds` install needs **no tokens** — `--secrets-provider systemd-creds --non-interactive` keeps the existing `.cred` files (nothing to prompt). `--help` lists every flag.
|
|
51
58
|
|
|
52
59
|
**Upgrade** is just re-running it: config merged (edits kept), your previously-chosen storage backend pre-selected, existing tokens kept. You can **switch backends** on any run — pick `file` and it decrypts the existing `systemd-creds` tokens into the file (no re-entry); pick `systemd-creds` and it encrypts the tokens into `.cred` files. `launch.sh` is no longer used — remove it after a successful run: `sudo rm -f /opt/gh-broker/launch.sh` (and `sudo rm -rf /etc/gh-broker/creds` only if you've moved fully to the `file` backend).
|
|
53
60
|
|
|
@@ -60,6 +67,39 @@ The installer asks which backend holds the tokens at rest:
|
|
|
60
67
|
|
|
61
68
|
Either way the **agent user cannot read the tokens** (that's filesystem perms, not encryption) and the **config stays token-free**. The choice only affects at-rest protection vs portability. See [`../docs/deploy-architecture.md`](../docs/deploy-architecture.md).
|
|
62
69
|
|
|
70
|
+
## Configuring the install
|
|
71
|
+
|
|
72
|
+
`config.example.json` ships only the generic defaults: `listen`, `upstream` and the `tools` allowlist. Everything specific to your deployment (owners, GHCR, App, npm, secrets) is supplied at install time. `config.sample.json` shows a complete config with placeholder values; nothing loads it.
|
|
73
|
+
|
|
74
|
+
Settings are layered, lowest precedence first:
|
|
75
|
+
|
|
76
|
+
1. `config.example.json` (shipped defaults),
|
|
77
|
+
2. the config already deployed on the host,
|
|
78
|
+
3. `--config <file>`,
|
|
79
|
+
4. command-line flags.
|
|
80
|
+
|
|
81
|
+
A scalar is replaced by a higher layer. `owners` and `ghcr.owners` are merged per owner, so adding an owner never drops the deployed ones. `tools` always comes from the example: the allowlist is a security control, so a `--config` file that contains `tools` is refused. The `--config` file is JSON in the deployed config's shape, holds only `cred:` references (a value shaped like a token is refused), and the merged result is validated by the real loader before anything is deployed. An upgrade run with no config flags keeps the deployed owners, `ghcr` and everything else as they are.
|
|
82
|
+
|
|
83
|
+
| Flag | Sets |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `--config <file>` | JSON config file merged on top of the deployed config |
|
|
86
|
+
| `--owner <org>` (repeatable) | `owners.<org>` = `cred:<org>-read` / `cred:<org>-write` |
|
|
87
|
+
| `--installation-id <org>=<id>` (repeatable) | `owners.<org>.installationId` |
|
|
88
|
+
| `--default-owner <org>` | `defaultOwner` (a first install with a single owner defaults to it) |
|
|
89
|
+
| `--listen <unix:path>` | `listen` |
|
|
90
|
+
| `--upstream <url>` | `upstream` |
|
|
91
|
+
| `--app-id <n>`, `--app-private-key <cred-name>` | `app.appId`, `app.privateKey` (`cred:gh-app-key` unless named) |
|
|
92
|
+
| `--secrets-provider <p>`, `--secrets-path <path>` | `secrets.provider`, `secrets.path` (path is for the `file` provider) |
|
|
93
|
+
| `--npm`, `--npm-scope`, `--npm-org`, `--npm-registry <url>`, `--npm-token-days` | the `npm` block, see [npm integration](#npm-integration-npm_scaffold) |
|
|
94
|
+
| `--ghcr-owner <org>`, `--ghcr-username <login>`, `--ghcr-image <glob>` (repeatable) | `ghcr.owners.<org>`; the credential is `cred:<org>-packages` |
|
|
95
|
+
| `--ghcr-drop-dir <path>` | `ghcr.dropDir` (default `/srv/gh-broker/ghcr-drop`) |
|
|
96
|
+
| `--ghcr-max-archive-bytes <n>` | `ghcr.maxArchiveBytes` |
|
|
97
|
+
| `--protect-release-tags`, `--no-protect-release-tags` | `ghcr.protectReleaseTags` |
|
|
98
|
+
|
|
99
|
+
Flags that build a single GHCR owner do so for one owner per run; use `--config` for several, or for a credential name other than the derived one.
|
|
100
|
+
|
|
101
|
+
With no owner anywhere (a first install with no flags and no `--config`), an interactive run asks for the GitHub owner name and then for its two tokens; `--non-interactive` fails and names `--owner` / `--config`.
|
|
102
|
+
|
|
63
103
|
## 3. Point Claude at the broker
|
|
64
104
|
|
|
65
105
|
The installer prints this; add it to the agent user's `~/.claude.json` `mcpServers`:
|
|
@@ -76,7 +116,25 @@ Re-login as the agent user first, so its `gh-broker-clients` membership is activ
|
|
|
76
116
|
|
|
77
117
|
## Adding an org later
|
|
78
118
|
|
|
79
|
-
|
|
119
|
+
Re-run the installer with `--owner <org>` (repeatable). The deployed owners stay, the new one is added with `cred:<org>-read` / `cred:<org>-write`, and the installer prompts for its two tokens. No code change.
|
|
120
|
+
|
|
121
|
+
## npm integration (`npm_scaffold`)
|
|
122
|
+
|
|
123
|
+
Optional. It lets `npm_scaffold` create new npm packages and their trusted publishers. The broker holds an npm granular access token; the installer creates it for you.
|
|
124
|
+
|
|
125
|
+
**Enable or rotate:** re-run the installer with `--npm` (a first interactive install also offers it). The installer then:
|
|
126
|
+
|
|
127
|
+
1. asks which scopes to allow (`--npm-scope @your-org`, repeatable; defaults to the existing setting, else `@<first owner>`),
|
|
128
|
+
and, with `--npm-org your-org` (repeatable), also grants the token org read-write on those orgs; the choice is saved as `npm.tokenOrgs` so a later rotation creates the same kind of token,
|
|
129
|
+
2. starts an npm web login and shows the login link, then **waits** until you approve it in a browser (the same flow as `npm login --auth-type=web`),
|
|
130
|
+
3. asks for your npm password and a one-time password (both hidden; the password is used for this one request and never stored), and creates a granular token: those scopes read-write, `--npm-token-days` lifetime (default 90), **no 2FA bypass**,
|
|
131
|
+
4. revokes the login session, stores the token as `npm-token` in the chosen credential backend, and adds `"npm": { "token": "cred:npm-token", "scopes": [...] }` to the config.
|
|
132
|
+
|
|
133
|
+
Without 2FA bypass the token can't publish or change trust settings on its own: every npm write `npm_scaffold` makes carries its own one-time password from you (one for the publish, one for the trusted publisher). Running `--npm` again creates a fresh token and replaces the stored one. The installer does not revoke the old token; it says so, and the old one stays valid until it expires or you revoke it in your npm account's Access Tokens settings.
|
|
134
|
+
|
|
135
|
+
**Unattended:** the web login needs a browser, so `--non-interactive --npm` takes an existing token instead, via `--secrets-file` (`{ "npm-token": "npm_…" }`) or env `GH_BROKER_SECRET_NPM_TOKEN`.
|
|
136
|
+
|
|
137
|
+
Without the `npm` block nothing changes: the installer says "not enabled", and the broker does not expose `npm_scaffold` at all (startup logs why). The token is an **optional credential**: if it is missing (skipped at the prompt, or an unattended run without it), the installer carries on and the broker starts normally with `npm_scaffold` hidden. Only the owners' PATs are required.
|
|
80
138
|
|
|
81
139
|
## Enabling `link_issue` (GitHub App) — DEFERRED
|
|
82
140
|
|