Skip to content

docs(cloud): say what attaching does to a credential's blast radius - #171

Merged
glennmichael123 merged 1 commit into
mainfrom
docs/attach-credential-reach
Aug 18, 2026
Merged

docs(cloud): say what attaching does to a credential's blast radius#171
glennmichael123 merged 1 commit into
mainfrom
docs/attach-credential-reach

Conversation

@glennmichael123

Copy link
Copy Markdown
Member

Addresses #169 (three of its four acceptance criteria; the fourth is called out below).

What was wrong

cloud.attachTo finds the owner's box by listing the provider's servers with the attaching project's own token. That listing is the whole mechanism, and it forces something the config never said: the owner's box must be visible to the attacher's credential, so both projects have to share one provider project. On Hetzner a Cloud API token is project-scoped with Read or Read & Write, has no per-resource scoping, and a deploy needs write.

The old docstring said "Requires read access via the same HCLOUD_TOKEN", which understates it twice: the access needed is write, and the reach is the whole provider project, not the one box.

Before After
loghq CI reaches the loghq box loghq CI reaches statushq, bughq, stacks, localtunnels
bughq CI reaches the bughq box bughq CI reaches all of the above

And the other half of the same decision, now stated: per-project isolation and attaching are mutually exclusive. An app kept in its own provider project cannot be attached at all, because its token cannot see the owner's box.

What this adds

  • The attachTo docstring now states the radius, the write requirement, and the mutual exclusivity.
  • A new docs/config.md section, "Attaching to another project's server", since attachTo appeared nowhere in docs/ at all.
  • describeCredentialReach() attributes every server a credential can enumerate to a project, keeping the owner's boxes and this project's own separate from the ones nobody asked to reach. Only that last number is an argument against attaching, so collapsing them would bury the point.
  • formatCredentialReach() renders sorted, run-to-run stable plan lines, and stays quiet with one summary line when the reach really is just the two projects being joined. A warning that fires every time is a warning that gets skipped.

Both are pure and structurally typed over { name, labels } rather than a Hetzner server, so any driver that can enumerate what its credential sees can report a radius.

Acceptance criteria

On that last box

I did not change the CloudDriver interface. Hetzner cannot express a narrower credential at all, so the only honest way to design that seam is against a provider that can (AWS IAM), and guessing the shape from the provider that cannot is how you get an abstraction that fits nobody. Making the reporting provider-agnostic is the part that is useful today and does not need that guess; I would rather leave the interface change to whoever does it against a real second credential model.

Verification

  • 10 new tests, including the issue's own five-box fleet: attaching loghq to statushq reports 4 servers reachable that neither project owns
  • Full suite 4005 pass, 0 fail; typecheck clean; lint 0 errors 0 warnings

Related: #168 / #170 covers the port collision on the same shared box.

`cloud.attachTo` resolves the owner's box by LISTING the provider's servers with
the attaching project's own token. That is the whole mechanism, and it forces a
consequence the config never stated: the owner's box has to be visible to the
attacher's credential, so both projects must share one provider project. On
Hetzner a Cloud API token is scoped to a project with Read or Read & Write and
has no per-resource scoping, and a deploy needs write.

So setting `attachTo` hands this project's CI write access over every server in
that provider project. Three apps that each owned one box become three pipelines
that each reach all three, plus anything else in the project. The old docstring
said "requires read access via the same HCLOUD_TOKEN", which understates it twice
over: the access needed is write, and the reach is the whole project rather than
the one box.

The reverse is worth stating too, because it is the other half of the same
decision: an app kept in its own provider project cannot be attached at all.
Isolation and co-hosting are mutually exclusive here, and that belongs in the
docs as a deliberate trade rather than something found out afterwards.

`describeCredentialReach()` attributes every server a credential can enumerate to
a project, keeping the owner's boxes and this project's own separate from the
ones nobody asked to reach - only that last number is an argument against
attaching, so collapsing them would bury the point. `formatCredentialReach()`
renders it as sorted plan lines, and stays quiet with a single summary line when
the reach really is just the two projects being joined; a warning that fires
every time is a warning that gets skipped.

Both are pure and structurally typed over `{ name, labels }` rather than a
Hetzner server, so any driver that can enumerate what its credential sees can
report a radius. That is deliberate groundwork for the issue's point that the
driver interface should not assume one all-powerful token is the only shape.

Refs #169, #167
@glennmichael123
glennmichael123 merged commit 71c7046 into main Aug 18, 2026
4 checks passed
@glennmichael123
glennmichael123 deleted the docs/attach-credential-reach branch August 18, 2026 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant