--- id: services title: The component that can mint a hostname is not the component serving agent data status: open crates: [didbot-serve, didbot-pds, didbot-tls, didbot-dns] dependsOn: [] exitCriterion: > The process serving `com.atproto.*` traffic holds neither the zone credential nor the ACME account key, and losing it does not stop a certificate from renewing or a name from resolving. --- # services [docs/deployment.md](../docs/deployment.md) states the current shape plainly: the zone credential "lives beside" the signing keys, "reachable from the process" because the server writes its own DNS records. One process holds the signing keys, the zone credential, the ACME account key and every agent's repository. Its own configuration-tiers table has one row for all of it. Compromise that process and you can serve arbitrary `did.json` documents, pass DNS-01 for any name in the zone, and sign as any account it holds — three different kinds of total control, from one exposure. This is not [fleet](fleet.md). Fleet is several whole personal data servers — separate zones, separate keys, separate operators' worth of blast radius, operated as a set from above. This is one deployment, decomposed so that the piece which can mint a hostname and answer a DNS-01 challenge is not the piece an attacker reaches by finding a bug in repository-write handling. Ten small blast radii and one smaller one are different problems, and an epic that tried to be both would serve neither well. ## What is one process today, and why that is the risk worth naming first `docs/deployment.md`'s posture table already grades this: moving agents off the server host is "the step that matters and the cheapest", because it moves the keys and the zone credential off the machine running agent code. That step says nothing about the server host itself, where the same three capabilities still sit together: - **DNS issuance.** `crates/didbot-dns`'s `Route53Dns` holds the credential that writes the zone — scoped to one hosted zone, per the IAM policy in `docs/deployment.md`, but reachable from whatever process imports the crate. - **TLS issuance.** `crates/didbot-tls` holds the ACME account key and orders certificates, including the wildcard pair [agent-accounts](agent-accounts.md) describes per zone. - **Repository serving.** `crates/didbot-pds` and `crates/didbot-serve` hold every account's signing keys and answer every `com.atproto.*` request, including from the public internet, unauthenticated, for a good fraction of the surface. Only the third of these needs to be reachable by an arbitrary caller. The first two need to be reachable by nothing except the provisioning and renewal paths that call them. - [ ] **Say which capability moves, and to what.** Splitting a crate boundary is not splitting a process boundary; `didbot-tls` and `didbot-dns` already are separate crates and that has not reduced the blast radius, because both still link into the one binary that serves requests. What has to be decided is whether issuance becomes a separate process on the same host, a separate host, or stays in-process behind a narrower internal interface — and that decision has to be made against a concrete threat (a bug in repository-write handling, not an insider) or it is architecture for its own sake. - [ ] **A provisioning and renewal interface the serving process calls, rather than a set of crates it links.** Provisioning already asks for a DNS write and a certificate; today that is a function call, and it would become a request to a component that does not also parse untrusted repository writes. - [ ] **Say what the split does not fix.** Splitting issuance out does not remove the zone credential from the deployment, and does not change that whoever holds the issuance component still controls every name under the zone. It narrows which failures reach that control, not how much control exists. - [ ] **Failure mode of the split itself.** An issuance component that is down cannot provision a new account or renew a certificate before expiry. [e-stop](e-stop.md) says the emergency stop has to work when the network to the operator's own server is unreachable; a services split has to say what provisioning does when its own issuance component is the thing that is unreachable, rather than silently blocking forever or silently falling back to holding the credential in-process again. - [ ] **Relate to `docs/deployment.md`'s tier table.** That table names three configuration tiers reachable from the host; this epic is about process tiers, which is a different axis, and the two need to agree rather than be read as the same idea twice. ## Done Nothing closed yet.