@cldmv/gh-broker 0.0.0-stage → 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.
Files changed (104) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +279 -2
  3. package/config.example.json +294 -0
  4. package/config.sample.json +45 -0
  5. package/deploy/README.md +150 -0
  6. package/deploy/gh-broker.service +57 -0
  7. package/deploy/install.mjs +409 -0
  8. package/deploy/install.sh +29 -0
  9. package/deploy/lib/config-merge.mjs +214 -0
  10. package/deploy/lib/credentials.mjs +143 -0
  11. package/deploy/lib/install-args.mjs +144 -0
  12. package/deploy/lib/npm-setup.mjs +164 -0
  13. package/deploy/lib/prompt.mjs +98 -0
  14. package/deploy/lib/secret-store.mjs +132 -0
  15. package/deploy/lib/smoke.mjs +137 -0
  16. package/deploy/lib/system.mjs +246 -0
  17. package/dist/index.mjs +24406 -0
  18. package/dist/index.mjs.map +7 -0
  19. package/docs/TOOLS.md +538 -0
  20. package/docs/agent-user-model.md +89 -0
  21. package/docs/changelog/v0/v0.2.0.md +26 -0
  22. package/docs/changelog/v0/v0.2.1.md +23 -0
  23. package/docs/changelog/v0/v0.2.2.md +23 -0
  24. package/docs/changelog/v0/v0.2.3.md +33 -0
  25. package/docs/changelog/v0/v0.2.4.md +25 -0
  26. package/docs/changelog/v0/v0.2.5.md +29 -0
  27. package/docs/changelog/v0/v0.3.0.md +84 -0
  28. package/docs/changelog/v0/v0.3.1.md +30 -0
  29. package/docs/changelog/v0/v0.3.2.md +24 -0
  30. package/docs/changelog/v0/v0.3.3.md +28 -0
  31. package/docs/changelog/v0/v0.4.0.md +58 -0
  32. package/docs/changelog/v0/v0.4.1.md +33 -0
  33. package/docs/changelog/v0/v0.4.2.md +45 -0
  34. package/docs/changelog/v0/v0.5.0.md +39 -0
  35. package/docs/changelog/v0/v0.5.1.md +28 -0
  36. package/docs/changelog/v0/v0.6.0.md +52 -0
  37. package/docs/changelog/v0/v0.6.1.md +48 -0
  38. package/docs/changelog/v0/v0.7.0.md +71 -0
  39. package/docs/changelog/v0/v0.8.0.md +115 -0
  40. package/docs/deploy-architecture.md +84 -0
  41. package/package.json +71 -5
  42. package/src/appauth.ts +56 -0
  43. package/src/audit.ts +34 -0
  44. package/src/availability.ts +42 -0
  45. package/src/catalog.ts +182 -0
  46. package/src/config.ts +157 -0
  47. package/src/index.ts +107 -0
  48. package/src/paths.ts +60 -0
  49. package/src/router.ts +299 -0
  50. package/src/secrets.ts +167 -0
  51. package/src/server.ts +91 -0
  52. package/src/types.ts +128 -0
  53. package/src/upstream.ts +85 -0
  54. package/src/verbs/ghcr/archive.ts +314 -0
  55. package/src/verbs/ghcr/config.ts +142 -0
  56. package/src/verbs/ghcr/jobs.ts +141 -0
  57. package/src/verbs/ghcr/push.ts +192 -0
  58. package/src/verbs/ghcr/registry.ts +275 -0
  59. package/src/verbs/ghcr/tar.ts +203 -0
  60. package/src/verbs/ghcr_push.ts +264 -0
  61. package/src/verbs/issue_labels.ts +329 -0
  62. package/src/verbs/link_issue.ts +84 -0
  63. package/src/verbs/npm-registry.ts +168 -0
  64. package/src/verbs/npm-tarball.ts +76 -0
  65. package/src/verbs/npm_scaffold.ts +325 -0
  66. package/src/verbs/release-analysis.ts +212 -0
  67. package/src/verbs/repo-tier.ts +90 -0
  68. package/src/verbs/report_ci_status.ts +358 -0
  69. package/src/verbs/report_release_prs.ts +238 -0
  70. package/src/verbs/report_security_alerts.ts +442 -0
  71. package/src/verbs/report_unlabeled.ts +194 -0
  72. package/src/verbs/rest-client.ts +181 -0
  73. package/src/verbs/review_thread.ts +138 -0
  74. package/tests/appauth.test.vitest.mts +76 -0
  75. package/tests/audit.test.vitest.mts +48 -0
  76. package/tests/catalog.test.vitest.mts +234 -0
  77. package/tests/config-merge.test.vitest.mts +325 -0
  78. package/tests/config.test.vitest.mts +388 -0
  79. package/tests/credentials.test.vitest.mts +127 -0
  80. package/tests/ghcr-archive.test.vitest.mts +439 -0
  81. package/tests/ghcr-config.test.vitest.mts +96 -0
  82. package/tests/ghcr-push.test.vitest.mts +526 -0
  83. package/tests/ghcr-registry.test.vitest.mts +206 -0
  84. package/tests/helpers/ghcr-fixtures.mts +373 -0
  85. package/tests/install-args.test.vitest.mts +152 -0
  86. package/tests/issue-labels.test.vitest.mts +362 -0
  87. package/tests/npm-scaffold.test.vitest.mts +479 -0
  88. package/tests/npm-setup.test.vitest.mts +196 -0
  89. package/tests/paths.test.vitest.mts +57 -0
  90. package/tests/prompt.test.vitest.mts +128 -0
  91. package/tests/release-prs.test.vitest.mts +247 -0
  92. package/tests/rest-client.test.vitest.mts +115 -0
  93. package/tests/router.test.vitest.mts +505 -0
  94. package/tests/run-vitest.mjs +51 -0
  95. package/tests/secret-store.test.vitest.mts +167 -0
  96. package/tests/secrets.test.vitest.mts +208 -0
  97. package/tests/security-alerts.test.vitest.mts +338 -0
  98. package/tests/server.test.vitest.mts +200 -0
  99. package/tests/smoke.test.vitest.mts +177 -0
  100. package/tests/system.test.vitest.mts +355 -0
  101. package/tests/upstream.test.vitest.mts +91 -0
  102. package/tests/verb-helpers.test.vitest.mts +377 -0
  103. package/tests/verbs.test.vitest.mts +624 -0
  104. package/tsconfig.json +21 -0
@@ -0,0 +1,89 @@
1
+ # The agent-as-separate-user model
2
+
3
+ gh-broker's security rests on one structural decision: **the agent runs as a different, unprivileged OS user than the broker that holds the credentials.** The agent gets to *act* on GitHub through the broker; it never gets to *hold* a token. This document is the worked example of that layout — the users, the group, the permissions that make the boundary real, and how to reproduce it. It is a reference setup, not a one-off.
4
+
5
+ ## Why a separate user at all
6
+
7
+ The credentials (per-org fine-grained PATs, and optionally a GitHub App key) live in a secret store on a root-owned, agent-unreadable path, read into the broker process's memory at start. The only thing that ever crosses to the agent is MCP frames over a Unix socket — bytes in, bytes out, no token.
8
+
9
+ That isolation is the entire security property, not a garnish on top of one. If the agent and the broker were the same user, that user could read the secret store, connect to the socket, *and* read the broker process's environment and memory — the design would be theater. Running them as two users, with the OS enforcing the split, is what turns "the agent can't exfiltrate the secret" from a hope into a mechanism.
10
+
11
+ ## The three roles
12
+
13
+ - **Agent user** — the unprivileged account the agent (Claude, or any autonomous client) runs as. It is a member of the *bridge group* so it can open the socket, and nothing more. It is **not** in the service group and has no path to the credentials.
14
+ - **Service user** — the dedicated account the broker daemon runs as. It owns the socket and runtime directory and is the only identity that can read and hold the PATs. It runs nothing else.
15
+ - **Bridge group** — a group whose *sole* purpose is socket access. Both users belong to it; the socket is `group=<bridge>, mode 0660`. Membership grants exactly one capability — connect to the socket — and grants nothing over the credentials.
16
+
17
+ ## Example layout (this deployment)
18
+
19
+ | Role | User | uid / gid | Relevant group memberships | Runs as agent? |
20
+ |---|---|---|---|---|
21
+ | Agent | `repo` | 1003 / 1003 | `repo`, `users`, `docker` (986, **rootless**), `repos` (1004), `gh-broker-clients` (984) | yes |
22
+ | Service | `gh-broker` | 995 / 985 | `gh-broker` (985), `gh-broker-clients` (984) | no — daemon only |
23
+
24
+ Bridge group: `gh-broker-clients` (gid 984) → members `gh-broker, repo`.
25
+
26
+ Filesystem and socket:
27
+
28
+ | Path | Owner:Group | Mode | Effect |
29
+ |---|---|---|---|
30
+ | `/etc/gh-broker` | `root:gh-broker` | `0750` | Agent (not in `gh-broker`) can't even traverse into it. |
31
+ | the token store — `creds/*.cred` (systemd-creds) or `secrets.json` (file) | `root:root` / `root:gh-broker` | `0400` / `0640` | The tokens, in whichever backend was chosen at install. Readable only by root / the `gh-broker` group; the agent (not in it) can't. Referenced by `cred:` names in the config. |
32
+ | `/run/gh-broker` | `gh-broker:gh-broker` | `0755` | Traversable so the socket can be reached. |
33
+ | `/run/gh-broker/gh-broker.sock` | `gh-broker:gh-broker-clients` | `0660` | Agent connects (it's in the bridge group); non-members can't. |
34
+
35
+ ## What makes the boundary real
36
+
37
+ Read these as the capability matrix — what each user actually can and cannot do:
38
+
39
+ | | Read the token store | Read broker env / memory | Open the socket | Ever hold a PAT |
40
+ |---|---|---|---|---|
41
+ | **Agent** (`repo`) | no — not in `gh-broker`; dir is `0750 root:gh-broker`, tokens root-owned | no — separate user, plus the unit's `ProtectHome`/`NoNewPrivileges`/etc. | **yes** — member of `gh-broker-clients` | **never** |
42
+ | **Service** (`gh-broker`) | yes — via systemd-creds (memory-only `$CREDENTIALS_DIRECTORY`) or the file, into memory only | yes (it is the process) | yes — it owns it | yes — in memory only, never on an agent-readable path |
43
+
44
+ Three independent gates, each verifiable:
45
+
46
+ 1. **Credentials** — `/etc/gh-broker` is `0750 root:gh-broker`. The agent user is deliberately *not* in the `gh-broker` group, so it cannot list or traverse the directory, let alone read the token store. Verify: as the agent user, `cat /etc/gh-broker/secrets.json` (or a `creds/*.cred`) → *Permission denied*.
47
+ 2. **Socket** — `0660 gh-broker:gh-broker-clients`. The agent is in `gh-broker-clients`, so it can `connect()`; a user in neither group cannot. The socket speaks MCP frames only — no credential material is ever written to it.
48
+ 3. **Process** — the broker runs under the hardened unit (`ProtectHome`, `ProtectSystem=strict`, `PrivateTmp`, `NoNewPrivileges`, restricted syscalls). A separate user can't `ptrace` it or read `/proc/<pid>/environ`, so the in-memory token isn't reachable that way either.
49
+
50
+ ## Host hardening that backs the split
51
+
52
+ The two-user split only holds if the agent user has no *other* route to root or to the service user's files:
53
+
54
+ - **No passwordless sudo for the agent.** If the agent could `sudo`, every gate above is moot. Verify: as the agent user, `sudo -n true` → fails.
55
+ - **Rootless docker (mandatory if the agent has docker access).** The agent here is in the `docker` group — which on a **rootful** daemon is root-equivalent (a container can bind-mount host `/` and read any file, including the creds). It is safe *only because docker runs rootless*: the group grants access to the agent's own userns-mapped daemon, not to a host-root daemon. If you put the agent user in a rootful `docker` group, you have handed it the credentials. Keep docker rootless, or keep the agent out of the docker group.
56
+ - **The agent never has the credentials on any readable path** — not in `~/.claude.json`, not in an env var, not in a file it owns. The socket bridge (`socat STDIO UNIX-CONNECT:…`) is the whole of its access.
57
+
58
+ ## Setting up the agent user
59
+
60
+ The agent account is a prerequisite you provide; `deploy/provision.sh` only wires it into the bridge group. Set it up as a plain unprivileged user:
61
+
62
+ ```sh
63
+ # An ordinary login user with NO sudo. (Add to `docker` only if docker is rootless.)
64
+ sudo useradd --create-home --shell /bin/bash agent # or reuse an existing unprivileged user
65
+ # Make sure it has no sudo rights:
66
+ sudo -l -U agent # should report it may run nothing, or fail
67
+ ```
68
+
69
+ Then provisioning adds it to the bridge group and the socket becomes reachable after a re-login:
70
+
71
+ ```sh
72
+ sudo ./deploy/provision.sh agent # arg = the agent user; adds it to gh-broker-clients
73
+ # re-login (or `newgrp gh-broker-clients`) so the membership is active
74
+ ```
75
+
76
+ Point the agent's MCP config at the socket via the stdio bridge (see `deploy/README.md` §4). Nothing else about the agent needs the broker's internals.
77
+
78
+ ## Anti-patterns
79
+
80
+ - **Running the agent as root** (or with sudo) — collapses every gate; root reads the creds directly.
81
+ - **Running the broker as the agent user** — the agent then owns the secret store and the process; there is no boundary left.
82
+ - **Adding the agent to the *service* group (`gh-broker`)** — it only needs the *bridge* group (`gh-broker-clients`). Membership in `gh-broker` grants traversal into `/etc/gh-broker`. Keep these two groups distinct and put the agent in the bridge group only.
83
+ - **Granting the agent read on `/etc/gh-broker/creds`** "for convenience" — the whole point is that it can't.
84
+ - **A rootful `docker` group for the agent** — see above; it's a silent root-equivalent escape around the socket.
85
+
86
+ ## See also
87
+
88
+ - `deploy/README.md` — prerequisites, provisioning, the systemd unit, and agent wiring.
89
+ - The full design + threat model lives outside this repo (the maintainer's `designs/github-mcp-broker.md`).
@@ -0,0 +1,26 @@
1
+ # gh-broker v0.2.0 Changelog
2
+
3
+ **Release Date**: August 2026
4
+ **Release Type**: Minor
5
+ **Branch**: `release/0.2.0`
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ The first substantive release of the credential-routing MCP broker. GitHub reads — and the first writes — are routed through the daemon so the agent acts without ever holding a token, with per-owner read/write PATs resolved by repo owner and mode. Shipped in [#5](https://github.com/CLDMV/gh-broker/pull/5).
12
+
13
+ ---
14
+
15
+ ## ✨ Features
16
+
17
+ - **Custom review-thread verbs** — `resolve_review_thread` / `reply_review_thread`, so a PR review thread (e.g. Copilot's) can be replied to or resolved via the GraphQL mutations on the write PAT, filling a gap the hosted MCP has no tool for (`7a47c21`).
18
+ - **`create_pull_request` allowlisted; `deploy-app.sh` syncs `config.json`** (`8ce78f7`).
19
+
20
+ ## 🐛 Bug Fixes
21
+
22
+ - **All-toolsets upstream** — route to the all-toolsets hosted MCP and skip allowlisted tools the upstream doesn't expose, so the advertised toolset matches what actually resolves (`8f4302d`).
23
+
24
+ ## 🔧 Other
25
+
26
+ - `link_issue` disabled in the allowlist as dead (`92b3840`); the `link_issue` path refactored to the Octokit SDK + `@octokit/auth-app` App token instead of hand-rolled fetch (`4c1e88f`).
@@ -0,0 +1,23 @@
1
+ # gh-broker v0.2.1 Changelog
2
+
3
+ **Release Date**: August 2026
4
+ **Release Type**: Patch
5
+ **Branch**: `release/0.2.1`
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ A dependency-maintenance release — no runtime behavior changed. Shipped in [#8](https://github.com/CLDMV/gh-broker/pull/8).
12
+
13
+ ---
14
+
15
+ ## 🔧 Dependencies
16
+
17
+ - Bump `@modelcontextprotocol/sdk` to `1.30.0` (and `fast-uri` to `3.1.5`) ([#7](https://github.com/CLDMV/gh-broker/pull/7), `8fd7e21`).
18
+
19
+ ---
20
+
21
+ ## Upgrade notes
22
+
23
+ - No breaking changes — drop-in for v0.2.0.
@@ -0,0 +1,23 @@
1
+ # gh-broker v0.2.2 Changelog
2
+
3
+ **Release Date**: August 2026
4
+ **Release Type**: Patch
5
+ **Branch**: `release/0.2.2`
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ A dependency-maintenance release — no runtime behavior changed. Shipped in [#11](https://github.com/CLDMV/gh-broker/pull/11).
12
+
13
+ ---
14
+
15
+ ## 🔧 Dependencies
16
+
17
+ - Bump the `@hono/node-server` transitive dependency to `2.0.12` ([#11](https://github.com/CLDMV/gh-broker/pull/11)).
18
+
19
+ ---
20
+
21
+ ## Upgrade notes
22
+
23
+ - No breaking changes — drop-in for v0.2.1.
@@ -0,0 +1,33 @@
1
+ # gh-broker v0.2.3 Changelog
2
+
3
+ **Release Date**: September 2026
4
+ **Release Type**: Patch
5
+ **Branch**: `release/0.2.3`
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ A CI-routing fix plus routine dependency and workflow maintenance — no runtime behavior changed. Shipped in [#35](https://github.com/CLDMV/gh-broker/pull/35).
12
+
13
+ ---
14
+
15
+ ## 🐛 Bug Fixes
16
+
17
+ ### Route the Required PR Check mirror to `cldmv-runners` on private repos ([#22](https://github.com/CLDMV/gh-broker/pull/22))
18
+
19
+ The local `✅ Required PR Check` mirror job is routed to the self-hosted `cldmv-runners` on private repos, so the required check actually runs (and posts) where GitHub-hosted runners aren't used — otherwise the ruleset's required check never reports and the PR can't merge (`13a4e1b`).
20
+
21
+ ## 🔧 CI & tooling
22
+
23
+ - Pass `BOT_NAME` / `BOT_EMAIL` to the v4 release and feature-PR workflows so changelog contributor lists are accurate; retrigger `feature-pr` against the fixed `@v4` ([#34](https://github.com/CLDMV/gh-broker/pull/34), `cf559cd`, `97bb0f1`).
24
+
25
+ ## 🔧 Dependencies
26
+
27
+ - Bump `typescript` `6.0.3` → `7.0.2` ([#21](https://github.com/CLDMV/gh-broker/pull/21)), `@octokit/auth-app` `8.2.0` → `8.3.0` ([#20](https://github.com/CLDMV/gh-broker/pull/20)), `@octokit/core` `7.0.7` → `7.0.8` ([#37](https://github.com/CLDMV/gh-broker/pull/37)), `@types/node` `26.1.0` → `26.4.0` ([#38](https://github.com/CLDMV/gh-broker/pull/38)), and a patch group of 4 updates ([#36](https://github.com/CLDMV/gh-broker/pull/36)).
28
+
29
+ ---
30
+
31
+ ## Upgrade notes
32
+
33
+ - No breaking changes — drop-in for v0.2.2. The change is CI-only.
@@ -0,0 +1,25 @@
1
+ # gh-broker v0.2.4 Changelog
2
+
3
+ **Release Date**: September 2026
4
+ **Release Type**: Patch
5
+ **Branch**: `release/0.2.4`
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ A CI-signing fix — no runtime behavior changed. Shipped in [#42](https://github.com/CLDMV/gh-broker/pull/42).
12
+
13
+ ---
14
+
15
+ ## 🔧 CI & tooling
16
+
17
+ ### Sign redirected security PRs ([#41](https://github.com/CLDMV/gh-broker/pull/41))
18
+
19
+ The hotfix-redirector now maps the `CLDMV_BOT_*` GPG secrets so that security PRs it redirects are GPG-signed (and therefore "Verified"), instead of landing unsigned and being blocked by the verified-signature merge rule (`e754c70`).
20
+
21
+ ---
22
+
23
+ ## Upgrade notes
24
+
25
+ - No breaking changes — drop-in for v0.2.3. The change is CI-only.
@@ -0,0 +1,29 @@
1
+ # gh-broker v0.2.5 Changelog
2
+
3
+ **Release Date**: September 2026
4
+ **Release Type**: Patch
5
+ **Branch**: `hotfixes` (shipped via the hotfixes lane)
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ A security-maintenance **hotfix** — shipped directly through the `hotfixes → master` lane rather than the `next` queue. No runtime behavior changed. Shipped in [#46](https://github.com/CLDMV/gh-broker/pull/46).
12
+
13
+ ---
14
+
15
+ ## 🔒 Security & supply chain
16
+
17
+ ### `npm_and_yarn` group bump ([#40](https://github.com/CLDMV/gh-broker/pull/40))
18
+
19
+ Bump the `npm_and_yarn` dependency group across 1 directory with 4 updates, clearing the group's open security advisories (`a401571`).
20
+
21
+ ## 🔧 Dependencies
22
+
23
+ - `npm_and_yarn` group, 4 updates ([#40](https://github.com/CLDMV/gh-broker/pull/40)).
24
+
25
+ ---
26
+
27
+ ## Upgrade notes
28
+
29
+ - No breaking changes — drop-in for v0.2.4.
@@ -0,0 +1,84 @@
1
+ # gh-broker v0.3.0 Changelog
2
+
3
+ **Release Date**: September 2026
4
+ **Release Type**: Minor
5
+ **Branch**: `release/0.3.0`
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ v0.3.0 replaces gh-broker's deploy tooling with a single idempotent **Node installer** that handles both first install and upgrade, makes credential storage a **backend the operator chooses at install** (host-bound `systemd-creds`, encrypted at rest, by default; or a portable plaintext file), and puts **platform-neutral path resolution** under the daemon.
12
+
13
+ Nothing on the broker's runtime request path changed — owner/mode → PAT routing, the default-deny allowlist, the custom review-thread verbs, and the token-free config model all behave exactly as in v0.2.x. This release is entirely about how the daemon is **deployed, upgraded, and how its PATs are stored, migrated, and rotated**. It also carries a large batch of installer/secret-handling hardening and the deploy-model documentation.
14
+
15
+ The immediately preceding release, **v0.2.5**, shipped the `npm_and_yarn` security group bump (#40) as a hotfix; v0.3.0 builds on top of it.
16
+
17
+ ---
18
+
19
+ ## ✨ Features
20
+
21
+ ### One Node installer — first install and upgrade in a single command (#45)
22
+
23
+ `deploy/install.mjs` replaces the old shell scripts (`launch.sh`, `provision.sh`, `deploy-app.sh`). A single idempotent command does first install **and** upgrade: it regenerates the deployed config by **merging shipped defaults over the current config** (never a blind overwrite, so an operator's settings survive an upgrade while new defaults are picked up), deploys the built `dist/`, installs the hardened unit, and runs a post-deploy MCP smoke test. It is interactive by default and **fully non-interactive** via CLI flags (`--agent-user`, `--secrets-provider`, `--secrets-file`, `--create-agent-user`, `-y/--non-interactive`) or `GH_BROKER_SECRET_<NAME>` environment variables for unattended runs.
24
+
25
+ ### Operator-chosen secret backend, behind a provider seam (#45)
26
+
27
+ At install the operator chooses how the PATs are stored at rest: **`systemd-creds`** (default — host-bound, encrypted, delivered into `$CREDENTIALS_DIRECTORY` via a `LoadCredentialEncrypted` unit drop-in) or a **portable plaintext `file`** (`0640 root:gh-broker`, platform-neutral). A `config.secrets.provider` seam and platform-neutral path resolution (`--config` / `GH_BROKER_CONFIG` / `GH_BROKER_SECRETS` → platform config dir → default) back both, and the daemon exposes `--print-paths` and `--check` so the installer resolves paths and validates the config through the daemon itself (one resolver, no drift).
28
+
29
+ ### In-place credential migration and rotation (#45)
30
+
31
+ Existing `systemd-creds` tokens are **auto-migrated** on upgrade — the installer decrypts and re-uses them without re-prompting when they're detectable. A re-run **rotates** a credential when a new value is supplied (via `--secrets-file` or `GH_BROKER_SECRET_<NAME>`), while a plain re-run keeps the existing encrypted cred as-is.
32
+
33
+ ### The unit runs node via a maintained symlink (#43, #45)
34
+
35
+ The systemd unit no longer hardcodes a versioned node path. #43 first resolved node from the system-wide nvm `default` alias; v0.3.0 finalizes this as the unit running **`/usr/local/bin/node`** (a maintained symlink), so bumping node needs no unit edit and the CLI and service share one indirection.
36
+
37
+ ---
38
+
39
+ ## 🐛 Bug Fixes
40
+
41
+ ### Secret, credential, and installer input handling hardened (#45)
42
+
43
+ A batch of correctness and safety fixes across the installer and secret layer:
44
+
45
+ - **Credential names** are constrained to a safe charset (`[A-Za-z0-9._-]`, rejecting `.`/`..`) on both the daemon and installer sides, so a `cred:` name can't traverse outside `$CREDENTIALS_DIRECTORY` / the secrets file or produce a malformed root-written systemd drop-in.
46
+ - **File-provider tokens** strip a single trailing newline (matching `systemd-creds`), so a copy/pasted token isn't silently mismatched at auth time, and a newline-only value is rejected as empty.
47
+ - **Config and `--secrets-file` validation** reject arrays (which `typeof === "object"` otherwise accepts) and empty values; `config.secrets.path` is rejected unless the provider is `file` (it would otherwise be silently ignored under `systemd-creds`).
48
+ - **CLI flag parsing** rejects a missing/flag-shaped value (so `--agent-user --secrets-provider file` can't mis-parse the flag as the username), and `--config` without a path value fails fast rather than silently falling back to the default.
49
+ - **Ownership operations fail fast** when a uid/gid can't be resolved, instead of silently leaving `/etc/gh-broker` / `/opt/gh-broker` mis-owned or running `chown NaN:NaN`.
50
+ - **Switching to `systemd-creds`** now zero-overwrites and removes any leftover plaintext secrets file, so "encrypted at rest" isn't undermined by a stale file.
51
+ - The interactive prompt **fails with guidance** (use `--secrets-file`/env) when asked for a multi-line App PEM, rather than capturing only its first line.
52
+ - The post-deploy **smoke test waits for the socket** instead of failing on the first connect during the daemon's startup network I/O.
53
+
54
+ ---
55
+
56
+ ## 🔧 CI & tooling
57
+
58
+ ### esbuild install script approved (#43)
59
+
60
+ `allowScripts` approves esbuild's install script so `npm ci` runs clean under npm 11's allow-scripts; lockfile integrity remains the gate.
61
+
62
+ ---
63
+
64
+ ## 📚 Documentation
65
+
66
+ - **NEW:** deploy architecture (`docs/deploy-architecture.md`) — the installer/upgrade flow, config-merge semantics, the secret-store backends, and the node-symlink model (#45).
67
+ - **NEW:** the **agent-as-separate-user isolation model** (`docs/agent-user-model.md`) — the users, bridge group, and permissions that make the boundary real, documented as a reproducible example setup (#43, #45).
68
+ - `deploy/README.md` rewritten around the installer workflow and its non-interactive flags; README status updated to "deployed / in production" (#45).
69
+ - The `link_issue` verb is **deferred** — its catalog advertising and router dispatch are commented out until the GitHub App path is wired; it is not advertised or callable (#45).
70
+ - **NEW:** [docs/changelog/v0/v0.3.0.md](./v0.3.0.md) — this changelog, and the start of gh-broker's per-version changelog convention.
71
+
72
+ ---
73
+
74
+ ## 🔧 Dependencies
75
+
76
+ _No dependency updates in this release._ The `npm_and_yarn` security group bump (#40) shipped separately as the **v0.2.5** hotfix.
77
+
78
+ ---
79
+
80
+ ## Upgrade notes
81
+
82
+ - **No runtime or API change** — the broker's request routing, default-deny allowlist, custom verbs, and token-free config model are identical to v0.2.x. There is nothing to change in how an agent talks to the broker.
83
+ - **The deploy process changed.** Install and upgrade with `sudo node deploy/install.mjs` (see `docs/deploy-architecture.md` and `deploy/README.md`); the old `launch.sh`, `provision.sh`, and `deploy-app.sh` are removed. Existing `systemd-creds` installs **auto-migrate** on upgrade — re-run the installer, no token re-entry needed.
84
+ - The secret backend is chosen at install; `systemd-creds` (encrypted at rest) remains the default.
@@ -0,0 +1,30 @@
1
+ # gh-broker v0.3.1 Changelog
2
+
3
+ **Release Date**: September 2026
4
+ **Release Type**: Patch
5
+ **Branch**: `release/0.3.1`
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ Toolchain-floor fix: `vitest` 5 requires Node `^20.19.0 || >=22.12.0`, which excludes plain `>=20.19.0`/`>=20`-style ranges that don't also cover the 22.x break point. Raises the real floor to match, in both the installer's own Node-version check and `package.json` `engines.node`.
12
+
13
+ ---
14
+
15
+ ## 🐛 Bug Fixes
16
+
17
+ - Installer's Node-version prerequisite check raised to `>=22.12.0`, matching what `vitest` 5 actually requires — a lower floor there let the installer proceed on a Node version the test toolchain can't run on.
18
+ - `engines.node` raised to `>=22.12.0` for the same reason.
19
+
20
+ ---
21
+
22
+ ## 🔧 Dependencies
23
+
24
+ - `vitest`, `@vitest/coverage-v8`, `@octokit/auth-app`, `@types/node` bumped to their latest compatible versions (#52).
25
+
26
+ ---
27
+
28
+ ## Upgrade notes
29
+
30
+ No breaking changes to the broker's runtime behavior — this only affects the Node version required to build/test/install it.
@@ -0,0 +1,24 @@
1
+ # gh-broker v0.3.2 Changelog
2
+
3
+ **Release Date**: September 2026
4
+ **Release Type**: Patch
5
+ **Branch**: `release/0.3.2`
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ Follow-up to v0.3.1's Node-floor raise: brings the CI test matrix itself up to the same `>=22.12.0` floor so it actually exercises the versions the toolchain requires, and groups related Dependabot updates so they land as one PR instead of several.
12
+
13
+ ---
14
+
15
+ ## 🔧 CI & tooling
16
+
17
+ - CI test matrix bumped to match the `>=22.12.0` engines floor raised in v0.3.1.
18
+ - Dependabot config groups the `eslint`/`prettier` families and the `vitest`/`@vitest/*` family into one bump each, instead of a separate PR per package.
19
+
20
+ ---
21
+
22
+ ## Upgrade notes
23
+
24
+ No breaking changes — CI/tooling only, no runtime code changed.
@@ -0,0 +1,28 @@
1
+ # gh-broker v0.3.3 Changelog
2
+
3
+ **Release Date**: September 2026
4
+ **Release Type**: Patch
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ v0.3.3 is a packaging-only release. The package's `publishConfig.access` changes from `restricted` to `public`, so the scoped `@cldmv/gh-broker` package is published as a public package. It shipped through the hotfixes lane ([#72](https://github.com/CLDMV/gh-broker/pull/72)). No runtime code changed.
11
+
12
+ ---
13
+
14
+ ## 🐛 Bug Fixes
15
+
16
+ ### Publish as a public scoped package ([#72](https://github.com/CLDMV/gh-broker/pull/72))
17
+
18
+ `package.json` declared `"publishConfig": { "access": "restricted" }`, which is the setting for a private scoped package and makes the registry treat a publish of `@cldmv/gh-broker` as one. It is now `"public"`.
19
+
20
+ ## 📚 Documentation
21
+
22
+ - **NEW:** [docs/changelog/v0/v0.3.3.md](./v0.3.3.md): this changelog.
23
+
24
+ ---
25
+
26
+ ## Upgrade notes
27
+
28
+ - No breaking changes, and no runtime change: the broker behaves exactly as in [v0.3.2](./v0.3.2.md).
@@ -0,0 +1,58 @@
1
+ # gh-broker v0.4.0 Changelog
2
+
3
+ **Release Date**: September 2026
4
+ **Release Type**: Minor
5
+ **Branch**: `release/0.4.0`
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ This release adds two report-style custom verbs — `report_unlabeled` and `report_ci_status` — plus a new way for the broker to enrich a proxied tool's response in place, and fixes three real bugs found while building and deploying them. Nothing about this release is breaking: every addition is a new verb or an opt-in parameter, and existing callers see no behavior change.
12
+
13
+ The headline capability is CI visibility without per-call log fetches: `report_ci_status` (and the same logic layered onto a normal `pull_request_read`) answers "does this PR have failing checks" at whatever detail level the caller asks for — counts only by default, escalating to which job failed, which step failed, and finally a tail of the failing job's actual log — and only ever pays for that detail on runs that are genuinely failing. Verified against a real failing PR (`CLDMV/droidsock#35`): correct pass/fail classification across five jobs (including three cancelled matrix legs), correct per-step failure detail, and a real log tail that surfaced the actual root cause (an `npm ERESOLVE` peer-dependency conflict) with no further digging required.
14
+
15
+ ---
16
+
17
+ ## ✨ Features
18
+
19
+ ### `report_unlabeled` — issues/PRs missing labels, one repo or every repo an owner has (#57)
20
+
21
+ Composes the already-allowlisted `list_issues`/`list_pull_requests` (plus an org-repo listing when `repo` is omitted) into a single report of unlabeled issues and/or PRs. Bounded by `maxRepos`/`perRepoLimit`/`maxApiCalls`, with a rate-limit floor read from the token's own `x-ratelimit-limit` header (not a hardcoded assumption about which token type is in use) so it backs off before GitHub does. Draft PRs are excluded by default (`includeDrafts` to override). Hitting a cap or the rate limit returns `truncated: true` with whatever was already found, rather than discarding partial results.
22
+
23
+ ### `report_ci_status` — tiered CI health for a PR, or every open PR in a repo (#61, #64)
24
+
25
+ Reports pass/fail/pending counts by default (tier 0), escalating on request to which run/job failed (tier 1), which step inside that job failed (tier 2), and a tail of the failing job's log (tier 3). Detail beyond tier 0 is only ever fetched for jobs that are actually failing, so a healthy PR costs exactly one call regardless of the requested verbosity. Tier 3 calls the hosted MCP's own `get_job_logs` tool (`failed_only: true` fetches every failing job in a run in one call) rather than a hand-rolled REST fetch — one call per failing run, not per job (#64).
26
+
27
+ ### `pull_request_read` gets an inline `ciVerbosity` parameter (#61)
28
+
29
+ A normal `pull_request_read` (`method: "get"`) can now carry CI status inline by passing `ciVerbosity` (0–3, same tiers as `report_ci_status`) and an optional `logLines`. This is the first tool the router's new **augment** dispatch mode powers: forward to the hosted MCP like a plain proxy, then post-process the result — gh-broker's own MCP contract for a proxied tool is not required to match the hosted MCP's shape verbatim. Opt-in only: omitting `ciVerbosity` is a byte-identical forward, so existing callers are unaffected.
30
+
31
+ ---
32
+
33
+ ## 🐛 Bug Fixes
34
+
35
+ ### `npm run dev` crashed outright (#63)
36
+
37
+ `node --experimental-strip-types` — the project's own `dev` script — rejects TypeScript constructor-parameter-property syntax, which `Router` and `AppAuth` both used. Invisible to `typecheck`/`build`/`test` since `tsc` and `esbuild` transform the code rather than only stripping types. Fixed in both places it appeared; verified against the real `dev` command, which now reaches `listening` where it previously crashed before any application code ran.
38
+
39
+ ### Installer left root-owned `dist/`/`node_modules/` in the checkout (#63)
40
+
41
+ The installer requires root, and its build-if-needed step ran `npm ci`/`npm run build` inside the repo checkout itself — not just the deploy target, which is chowned correctly elsewhere. That left build output owned by root in the operator's own working tree, breaking their next plain `npm run build` with `EACCES`. Fixed at the root cause: the build now runs via `sudo -u '#<uid>'`, with the uid read directly from the install script's own file ownership — no separate "agent user" concept needed, and no `chown` anywhere (not cross-platform, and unnecessary once nothing is ever created with the wrong owner to begin with). Detects and rebuilds a stale `dist/`/`node_modules/` left by an older version of this script, so re-running the installer is self-healing with no manual cleanup step.
42
+
43
+ ### Rate-limit floor was a hardcoded guess (#57)
44
+
45
+ `report_unlabeled`'s proactive rate-limit backoff was a flat `100` with no derivation from GitHub's actual limit — wrong for any token whose real quota differs from a 5,000/hr PAT. Now reads `x-ratelimit-limit` off every response and floors at 2% of that, with an absolute minimum for low-limit tokens. `report_ci_status` shares the same rate-limit/call-budget module (`rest-client.ts`, extracted once a second verb needed the identical logic).
46
+
47
+ ---
48
+
49
+ ## 🔧 CI & tooling
50
+
51
+ - Extracted the rate-limit-floor + call-budget machinery shared by `report_unlabeled` and `report_ci_status` into `src/verbs/rest-client.ts`.
52
+ - `router.ts` gained a third dispatch mode, `augment`, alongside the existing `custom` (fully local) and plain-`forward` paths — logged in the audit trail the same way as the other two.
53
+
54
+ ---
55
+
56
+ ## Upgrade notes
57
+
58
+ No breaking changes — every addition here is a new verb or an opt-in parameter on an existing one; nothing about an existing call's behavior changes unless the new parameters are used.
@@ -0,0 +1,33 @@
1
+ # gh-broker v0.4.1 Changelog
2
+
3
+ **Release Date**: October 2026
4
+ **Release Type**: Patch
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ v0.4.1 keeps the toolchain current and adopts the shared CLDMV file-header setup. Runtime dependencies move by one patch (`@modelcontextprotocol/sdk` 1.30.0 to 1.30.1), TypeScript goes back to 6, and every source, test and workflow file now carries a uniform header maintained by `@cldmv/fix-headers`. No broker behavior changed.
11
+
12
+ ---
13
+
14
+ ## 🔧 CI & tooling
15
+
16
+ ### Uniform file headers with `@cldmv/fix-headers` ([#84](https://github.com/CLDMV/gh-broker/pull/84))
17
+
18
+ The repository adopts the shared CLDMV header configuration. `@cldmv/fix-headers` 2.1.1 and `@cldmv/configs` 1.2.0 are new dev dependencies, `.configs/fix-headers.json` holds the checked-in config, and a new `npm run fix:headers` script restamps headers. Every file under `src/`, `tests/`, `deploy/` and `.github/workflows/` was restamped in one pass, which is why the diff touches more than fifty files. The change is comments only.
19
+
20
+ ## 🔧 Dependencies
21
+
22
+ - TypeScript is back on 6.0.3 ([#82](https://github.com/CLDMV/gh-broker/pull/82)). TypeScript 7 is not usable here until 7.1 ships a compiler API.
23
+ - The patch group ([#80](https://github.com/CLDMV/gh-broker/pull/80)): `@modelcontextprotocol/sdk` 1.30.0 to 1.30.1, `@types/node` 26.6.2 to 26.6.3, and `vitest` and `@vitest/coverage-v8` 5.0.1 to 5.0.2 (both dev-only).
24
+
25
+ ## 📚 Documentation
26
+
27
+ - **NEW:** [docs/changelog/v0/v0.4.1.md](./v0.4.1.md): this changelog.
28
+
29
+ ---
30
+
31
+ ## Upgrade notes
32
+
33
+ - No breaking changes, and no change in how the broker behaves. Drop-in for [v0.4.0](./v0.4.0.md).
@@ -0,0 +1,45 @@
1
+ # gh-broker v0.4.2 Changelog
2
+
3
+ **Release Date**: October 2026
4
+ **Release Type**: Patch
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ v0.4.2 patches the three transitive dependencies behind four open Dependabot alerts, bumps the MCP SDK and the test runner, and fixes how the CI mirror job reports the required check. The broker's behavior is unchanged.
11
+
12
+ ---
13
+
14
+ ## 🔒 Security & supply chain
15
+
16
+ ### Patched transitive dependencies ([#90](https://github.com/CLDMV/gh-broker/pull/90))
17
+
18
+ A lockfile-only update moves three packages that the MCP SDK pulls in to their patched releases:
19
+
20
+ - `ip-address` 10.7.0 to 10.7.3, which fixes [alert 18](https://github.com/CLDMV/gh-broker/security/dependabot/18) (a long input could stall the process) and [alert 19](https://github.com/CLDMV/gh-broker/security/dependabot/19) (`isInSubnet` could admit an address from a different address family).
21
+ - `fast-uri` 3.1.7 to 3.1.8, which fixes [alert 20](https://github.com/CLDMV/gh-broker/security/dependabot/20) (inconsistent host case normalization).
22
+ - `hono` 4.13.5 to 4.13.12, which fixes [alert 21](https://github.com/CLDMV/gh-broker/security/dependabot/21) (the `hono/jsx` unescaped-string XSS).
23
+
24
+ None of them is a direct dependency. The broker is a local stdio proxy, so exposure was low, but the alerts are now closed on the release branch.
25
+
26
+ ## 🔧 CI & tooling
27
+
28
+ ### The required-check mirror no longer skips ([#87](https://github.com/CLDMV/gh-broker/pull/87), [#89](https://github.com/CLDMV/gh-broker/pull/89))
29
+
30
+ The `✅ Required PR Check` mirror job in `ci.yml` was skipped on in-repo feature PRs. GitHub treats a skipped required check as satisfied, and it never evaluates a skipped job's `name:`, so the PR showed the raw expression as a check name. The job now runs on every path with `if: always()`. On an in-repo PR it reports under a readable, non-required name (`⏭️ Required PR Check (reported by the push run)`) and passes as a no-op, while the push run still posts the required name. It still waits for the full test matrix through `needs: ci`.
31
+
32
+ ## 🔧 Dependencies
33
+
34
+ - `@modelcontextprotocol/sdk` 1.30.1 to 1.31.0 ([#85](https://github.com/CLDMV/gh-broker/pull/85)).
35
+ - `@cldmv/vitest-runner` 1.2.0 to 1.5.1, dev-only ([#85](https://github.com/CLDMV/gh-broker/pull/85)).
36
+
37
+ ## 📚 Documentation
38
+
39
+ - **NEW:** [docs/changelog/v0/v0.4.2.md](./v0.4.2.md): this changelog.
40
+
41
+ ---
42
+
43
+ ## Upgrade notes
44
+
45
+ - No breaking changes. Drop-in for [v0.4.1](./v0.4.1.md).
@@ -0,0 +1,39 @@
1
+ # gh-broker v0.5.0 Changelog
2
+
3
+ **Release Date**: October 2026
4
+ **Release Type**: Minor
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ v0.5.0 adds two report verbs that answer fleet-wide questions in one call instead of one call per repo: `report_security_alerts` for open Dependabot, CodeQL and secret-scanning alerts, and `report_release_prs` for open release PRs and whether each has its changelog. Existing verbs behave as before, with one fix in how rate limits are recognised.
11
+
12
+ ---
13
+
14
+ ## ✨ Features
15
+
16
+ ### `report_security_alerts` ([#91](https://github.com/CLDMV/gh-broker/pull/91))
17
+
18
+ Lists every open Dependabot, code-scanning (CodeQL) and secret-scanning alert for one repo or a whole owner. It reads the org-level alert endpoints in a handful of calls and falls back to a per-repo sweep for any alert type the token cannot read org-wide. Each alert is normalized to one shape with type-specific fields (package, patched version and manifest for Dependabot; rule and location for CodeQL; secret type and validity for secrets), sorted worst first. Secret values are never returned. Results carry `scope` (org or per-repo for each type) and `disabled` (repos where a type is off), and the usual `truncated` / `truncatedReason` when a cap or the rate limit is hit. An alert's state tracks the default branch, so one already fixed on `next` still reads open until it ships.
19
+
20
+ ### `report_release_prs` ([#91](https://github.com/CLDMV/gh-broker/pull/91))
21
+
22
+ Finds every open release PR (label `release`: the `next` and `hotfixes` lines into `master`) for an owner and reports whether each one's per-version changelog file (`docs/changelog[s]/v<major>/vX.Y.Z.md`) exists on the release branch, as `ready`, `needs-changelog` or `unknown-version`. It costs one search call plus a few calls per release PR, and `onlyMissing` narrows the list to the ones that still need a changelog.
23
+
24
+ ## 🐛 Bug Fixes
25
+
26
+ ### A permission 403 is no longer mistaken for a rate limit ([#91](https://github.com/CLDMV/gh-broker/pull/91))
27
+
28
+ The shared REST client treated every `403` as throttling, so a "feature not enabled" answer (code scanning off on a repo) would have stopped a whole scan. A `403` now counts as a rate limit only when GitHub signals one: a `Retry-After` header, an exhausted `x-ratelimit-remaining`, or a rate-limit or abuse message. Everything else reaches the caller with its status. This also applies to `report_unlabeled` and `report_ci_status`. Dependabot's cursor pagination and the repo-listing helper moved into the shared client with it.
29
+
30
+ ## 📚 Documentation
31
+
32
+ - **NEW:** [docs/changelog/v0/v0.5.0.md](./v0.5.0.md): this changelog.
33
+ - The two verbs are documented in `docs/TOOLS.md` and the README.
34
+
35
+ ---
36
+
37
+ ## Upgrade notes
38
+
39
+ - No breaking changes. The new verbs appear in the tool list after you redeploy and the client reconnects, and need no config change.
@@ -0,0 +1,28 @@
1
+ # gh-broker v0.5.1 Changelog
2
+
3
+ **Release Date**: October 2026
4
+ **Release Type**: Patch
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ v0.5.1 stops the installer from asking where credentials are stored when the install already has an answer. Upgrading no longer needs a keypress, and the broker itself is unchanged.
11
+
12
+ ---
13
+
14
+ ## 🐛 Bug Fixes
15
+
16
+ ### The installer keeps the existing secrets backend ([#95](https://github.com/CLDMV/gh-broker/pull/95))
17
+
18
+ `deploy/install.mjs` asked "How should the PAT credentials be stored at rest?" on every interactive run, and only `--non-interactive` skipped it. It now works out the backend already in use: the provider recorded in the deployed config, otherwise encrypted `.cred` files (systemd-creds) or tokens in the secrets file. It logs `keeping existing backend: <name>` and carries on. A first install, or a state where both backends hold credentials and nothing is recorded, still prompts, and `--secrets-provider` still overrides. Six unit tests cover the detection.
19
+
20
+ ## 📚 Documentation
21
+
22
+ - **NEW:** [docs/changelog/v0/v0.5.1.md](./v0.5.1.md): this changelog.
23
+
24
+ ---
25
+
26
+ ## Upgrade notes
27
+
28
+ - No breaking changes. Re-run the installer as usual; it keeps your current credential backend.