From 8e23feb01bceeab1d8763a4727ecf87811bf82e8 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 2 Oct 2026 17:48:27 +0200 Subject: [PATCH] github: PATs live in swarm bao; the agent fetches them itself An operator links an agent's GitHub personal access token in the swarm UI (LinkGithubAccountForm, "link github account" on /agents). swarm-controller's PUT /api/hives/{hive}/agents/{agent}/github-account stores it at swarm/agents//github-token (swarm_secret_client::github), a flat leaf under the agent's prefix that the agent's existing read grant already covers: no policy change, and no list grant, since there is one token per agent. In the agent, hive-agent-github-token (oneshot + 2-minute timer, as the agent user, under its own store certificate, ordered before hive-github-notify) reads that path and writes /github-token, 0600 and agent-owned, the file the gh wrapper, git credential helper and hive-github-notify already read. It replaces the file by rename only when the bytes changed and never deletes it: a hive-written github-token stays until a token is linked in the swarm UI. It is installed only with a store address and services.hyperhive.agent.github.enable. Removed: the dashboard's CR3D3NTIALS page (credentials.html/js/css, its build entries and H0M3 tile; GITHUB was its only tab), hive-c0re's dashboard/matrix_accounts.rs with GET/POST /api/github-account, priv_client::write_agent_github_token, the host socket's SetAgentGithubToken and `hivectl github set-token`, and hive-priv's WriteAgentGithubToken with write_agent_state_file, its only caller gone. Docs: integrations/github.md and swarm/ui.md describe the swarm path, swarm/credentials.md gains the store-path row, and the hive UI docs, hivectl docs and security.md's hive-priv table drop the removed pieces. Closes #4347 --- docs/integrations/github.md | 43 ++-- docs/swarm/credentials.md | 1 + docs/swarm/ui.md | 26 ++- docs/tools/hivectl-cli.md | 36 ---- docs/tools/hivectl.md | 24 --- docs/trust-boundary/security.md | 1 - docs/web-ui/README.md | 12 +- docs/web-ui/dashboard.md | 33 +-- docs/web-ui/shape.md | 4 +- frontend/packages/dashboard/build.mjs | 5 +- .../packages/dashboard/src/credentials.css | 94 -------- .../packages/dashboard/src/credentials.html | 87 -------- .../packages/dashboard/src/credentials.js | 202 ------------------ frontend/packages/dashboard/src/index.html | 10 - .../shared/src/tabs/hive-tab-strip.js | 2 +- .../src/pages/LinkGithubAccountForm.tsx | 109 ++++++++++ .../swarm-ui/src/pages/agents/AgentsPage.tsx | 33 +++ hive-c0re/src/dashboard/matrix_accounts.rs | 100 --------- hive-c0re/src/dashboard/mod.rs | 13 +- hive-c0re/src/priv_client.rs | 19 -- hive-c0re/src/server.rs | 13 -- .../src/bin/hive-github-notify/main.rs | 11 +- hive-host-sock/src/lib.rs | 6 - hive-priv-sock/src/lib.rs | 16 -- hive-priv/src/main.rs | 41 +--- hivectl/README.md | 3 +- hivectl/src/cli.rs | 29 --- hivectl/src/github.rs | 48 ----- hivectl/src/main.rs | 15 +- hivectl/src/util.rs | 2 +- nix/agent-modules/default.nix | 1 + nix/agent-modules/github-token.nix | 154 +++++++++++++ nix/agent-modules/github.nix | 17 +- nix/checks.nix | 4 + nix/host-modules/hyperhive.nix | 5 +- nix/module-eval/agent-github-bao.nix | 114 ++++++++++ swarm-controller/README.md | 6 +- swarm-controller/src/github_account.rs | 176 +++++++++++++++ swarm-controller/src/main.rs | 2 + swarm-secret-client/src/github.rs | 128 +++++++++++ swarm-secret-client/src/lib.rs | 3 +- 41 files changed, 803 insertions(+), 845 deletions(-) delete mode 100644 frontend/packages/dashboard/src/credentials.css delete mode 100644 frontend/packages/dashboard/src/credentials.html delete mode 100644 frontend/packages/dashboard/src/credentials.js create mode 100644 frontend/packages/swarm-ui/src/pages/LinkGithubAccountForm.tsx delete mode 100644 hive-c0re/src/dashboard/matrix_accounts.rs delete mode 100644 hivectl/src/github.rs create mode 100644 nix/agent-modules/github-token.nix create mode 100644 nix/module-eval/agent-github-bao.nix create mode 100644 swarm-controller/src/github_account.rs create mode 100644 swarm-secret-client/src/github.rs diff --git a/docs/integrations/github.md b/docs/integrations/github.md index a6242c0f..88fccf27 100644 --- a/docs/integrations/github.md +++ b/docs/integrations/github.md @@ -5,9 +5,9 @@ HTTPS, both authenticated by an operator-supplied personal access token (PAT) — so it can run GitHub API calls and push commits without any manual `gh auth login`. -Provisioning is UI-driven: paste a PAT into the agent's credentials tab -and it works. No per-agent nix declaration, no rebuild — hive-c0re -injects the token into the agent's state dir out of band. +Provisioning is UI-driven: link a PAT to the agent in the swarm UI and it +works. No per-agent nix declaration, no rebuild — the agent fetches the token +from the swarm secret store itself. ## Enabling @@ -15,8 +15,7 @@ injects the token into the agent's state dir out of band. The integration is **on by default** for every agent (`services.hyperhive.agent.github.enable = true`), inert until the operator provisions a PAT. No per-agent declaration is -needed — an agent gains GitHub by having a PAT written to its token -file. +needed — an agent gains GitHub by having a PAT stored for it. @@ -27,29 +26,31 @@ services.hyperhive.github.enable = false; ``` hive-c0re's meta-flake renderer then injects `services.hyperhive.agent.github.enable = false` -into every agent, so no agent ships the `gh` wrapper or credential helper. +into every agent, so no agent ships the `gh` wrapper, the credential helper +or the token fetch. (`services.hyperhive.agent.github.enable` also exists per-agent for completeness, but the hive-wide host switch is the intended control.) -github.com only. The token **value** never touches nix — it's written to -`/github-token` separately (see [Provisioning](#provisioning)). +github.com only. The token **value** never touches nix — the agent writes it +to `/github-token` at runtime (see [Provisioning](#provisioning)). ## Provisioning -The PAT is operator-supplied. The primary path is the **dashboard -credentials tab** (github sub-tab): paste the PAT for an agent and submit -(`POST /api/github-account`). A CLI path also exists for -recovery/scripting: +The PAT is operator-supplied. In the [swarm UI](../swarm/ui.md#linking-external-accounts), +open the agent on `/agents`, choose **link github account** and paste the PAT +(`PUT /api/hives/{hive}/agents/{agent}/github-account`). swarm-controller +stores it at `swarm/agents//github-token` in the swarm secret store; +no hive writes it. One token per agent: linking again replaces it, +and no route hands it back. -```sh -hivectl github set-token --token-stdin # paste the PAT on stdin (preferred) -hivectl github set-token --token # inline (visible in shell history) -``` - -Either path has hive-c0re delegate the write to hive-priv, which stores the -file `0600` owned by the agent (so the container can read it) — the same -credential-injection path as forge/matrix tokens. See -[hivectl → GitHub](../tools/hivectl.md#github). +The agent's `hive-agent-github-token` unit reads that path under the +agent's own store certificate and writes `/github-token` (`0600`, +owned by the agent), on boot and every two minutes, replacing the file only +when the token changed. It needs a store identity +(`services.hyperhive.agent.bao.addr`); an agent without one gets no token. +The unit never deletes the file: a `github-token` already in place stays +when the store holds none or doesn't answer. Where the token lives and who +reads it: [credentials.md](../swarm/credentials.md). ## Security diff --git a/docs/swarm/credentials.md b/docs/swarm/credentials.md index 8a216831..5f5650a8 100644 --- a/docs/swarm/credentials.md +++ b/docs/swarm/credentials.md @@ -69,6 +69,7 @@ of the cell says how. | `swarm/agents//queue` | `swarm-controller`, at agent creation | `hive-agent` in the agent container, under the agent's own certificate, held in memory — the identity it presents to the swarm queue, naming that one agent rather than its hive | ✅ `swarm-controller`'s five-minute pass re-mints a live agent's secret once it's 45 days old by `minted_at` on the stored object; a secret with no `minted_at` gets one stamped, value unchanged. The pass skips agents declared `Destroyed` — declaring an agent destroyed deletes every version of the path instead, the undo of the mint rather than another one | ✅ `hive-agent` reads the path before its first connect and again on every reconnect attempt, so a reconnect after a re-mint presents the new secret. An open connection keeps the secret it connected with; after a revocation the agent keeps retrying under the queue client's backoff | | `swarm/agents//forge-token` | `swarm-controller`, at agent creation and in a pass every 5 minutes over every agent with a store identity | the agent container itself, under its own certificate, fetched to `/run/hive-agent-forge-token/token` | ✅ the controller re-mints when the stored token is missing or no longer matches the forge (last eight characters and scopes) | ✅ the agent re-fetches on a 10-minute timer | | `swarm/agents//forge/