From 8ad2af735ef9d1ef2794d4c6ac7ae4023d438496 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 3 Oct 2026 13:29:26 +0200 Subject: [PATCH] swarm-controller: refuse linking over an existing account The matrix, forge and github link routes wrote their credential unconditionally, so linking a name that was already linked replaced the working account. For matrix that lost the device the agent's crypto store belongs to (#4838). Each route now reads the account's store path first and answers 409, naming the existing account, when something is stored there. Nothing is written. Replacing an account takes the delete from #4899, then a link. The matrix route checks before password mode's login, so a refused link mints no new device at the homeserver. The check is a read then a write, not an atomic step; two concurrent links to one name can still both pass it. Closes #4856 --- docs/integrations/github.md | 4 +- docs/swarm/credentials.md | 4 +- docs/swarm/ui.md | 13 +- .../src/pages/LinkForgeAccountForm.tsx | 4 +- .../src/pages/LinkGithubAccountForm.tsx | 4 +- .../src/pages/LinkMatrixAccountForm.tsx | 4 +- swarm-controller/src/forge_account.rs | 55 +++++++-- swarm-controller/src/github_account.rs | 53 ++++++-- swarm-controller/src/linked_accounts.rs | 109 ++++++++++++++-- swarm-controller/src/matrix_account.rs | 116 ++++++++++++++---- 10 files changed, 303 insertions(+), 63 deletions(-) diff --git a/docs/integrations/github.md b/docs/integrations/github.md index 88fccf27..7dfb7a51 100644 --- a/docs/integrations/github.md +++ b/docs/integrations/github.md @@ -40,8 +40,8 @@ The PAT is operator-supplied. In the [swarm UI](../swarm/ui.md#linking-external- 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. +no hive writes it. One token per agent: swarm-controller refuses to link +another until you delete the stored one, and no route hands it back. The agent's `hive-agent-github-token` unit reads that path under the agent's own store certificate and writes `/github-token` (`0600`, diff --git a/docs/swarm/credentials.md b/docs/swarm/credentials.md index 89e63765..8573cf41 100644 --- a/docs/swarm/credentials.md +++ b/docs/swarm/credentials.md @@ -64,8 +64,8 @@ of the cell says how. | `swarm/agents//bao-mtls` | the store's agent PKI mount (`deploy.bao.agentPkiMountPath`), which generates the key, at `swarm-controller`'s request at agent creation | `hive-c0re`, under the hive's own certificate, when it writes the agent's container config | ✅ `swarm-controller`'s five-minute pass re-issues a live agent's leaf once it's past half its validity (45 of 90 days, read from the certificate itself) | ❌ `hive-c0re` reads it when it writes the container config, so the agent presents a new leaf from its next start; the old leaf stays valid until it expires | | `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/