Skip to content

feat: a signed plane refuses a roster its own account may write - #96

Merged
karauda merged 3 commits into
mainfrom
feat/s24-a
Nov 10, 2026
Merged

karauda merged 3 commits into
mainfrom
feat/s24-a

Conversation

@karauda

@karauda karauda commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Under approvals.authority: signed a plane now refuses to start when its own account may write the trust.roster file or a directory on its path; doctor fails its trust check the same way. Before, that account could put an older roster back. Operator commands also say which roster they judged with.

Roster placement (cmd/guardana-gateway):

  • trust.roster joins the configuration file, the root key, the pause file, the route, the stops and the run records in checkSignedStart.
  • A classification test walks every file key of gatewayconfig.Fields(). Each key is either checked at a signed start or named with the reason it is not. It runs over a full signed tree and over the minimal one, so a check that depends on another key being set fails.
  • Doctor reads the signed stop list as a plane does. A test runs it with the one-account switch off and requires doctor to name what the plane's own account owns.

Operator commands (cmd/guardana-control):

  • pause, stops, runs open, runs close, supervise and react print, on standard error and before they sign or list: judged with roster <id> serial <n>, expires <time>. The expiry comes from the document. Their judgement has no roster floor; the plane's is the one that counts.
  • runs open --key reads the roster and the key before it makes an empty directory a signed layout.

Stated limit (ADR-0071, ADR-0073, threat model, status): the plane's account still writes trust.floor_dir. By raising the roster floor it can withhold a newer roster until the held one expires. What this change adds is that it can no longer put an older roster in place.

Pages: ADR-0071, 0073 and 0076 amended, glossary, threat model, run-the-gateway, status, CHANGELOG (Breaking).

Verified:

  • Each of these mutants fails a test: the roster entry dropped; the roster checked only when pause.file is set; doctor taking a list the plane's account owns; the judged line missing, off by one serial, or with its expiry read from the clock; the layout written before the roster is read; react's guard against the plane's account turned into a no-op; react not printing the line.
  • Test binaries for both commands (whole), internal/trust, internal/console, internal/core/approval, internal/metrics, internal/docscheck and internal/core.
  • make vet, check-imports, check-brand, check-sizes, golangci-lint.

Not verified or not covered:

  • On one account, react's guard is replaced by a test variable in the roster line test. The guard itself has its own test.
  • A trust.roster path that is not clean (a link followed by ..) is judged through the link and read cleaned. The plane's account cannot supply such a path. This is recorded for a later release.

docs-impact: 0 uncovered surfaces; the frozen paths are the three amended ADRs and status. Suspect pages read: reaction-signed (commands say on standard error what they signed: still true), runs, supervision, the guides; none made false.

Under the signed authority the plane now refuses to start when its own
account can write trust.roster or a directory on its path, as it already
does for the configuration, the root key, the pause file, the route, the
stop list and the run records. A test classifies every file key the signed
authority reads as checked or plane-writable, so a new one cannot be missed.

The operator commands that judge with --roster say on standard error which
roster they judged, its serial and its expiry, before they sign or list.
Doctor's signed route and stop list reads get a test, and stops init
--carry runs as a command in a test.

Signed-off-by: Konrad Karauda <konrad@karauda.com>
…he seam

The plane's account still writes trust.floor_dir: raising the roster floor
withholds a newer roster until the held one expires. The pages now say
so, and that what the placement check adds is that the plane's account can
no longer put an older roster in place.

Doctor's signed stop list read is a method with its own test run with the
plane-owned switch off, so a doctor that takes a list the plane's own
account owns fails a test. react's guard against the plane's account is a
variable, so a test on one account reaches the roster line it prints. The
classification test also runs over a minimal signed tree, so a check that
depends on another key being set fails it.

Signed-off-by: Konrad Karauda <konrad@karauda.com>
Signed-off-by: Konrad Karauda <konrad@karauda.com>
@karauda
karauda requested a review from a team November 10, 2026 19:33
@karauda
karauda requested a review from a team as a code owner November 10, 2026 19:33
@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
cb3f36c 2026-11-10T19:34:11.253Z View logs ↗

@karauda
karauda merged commit e502561 into main Nov 10, 2026
12 of 13 checks passed
@karauda
karauda deleted the feat/s24-a branch November 10, 2026 19:51
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