carlos / platform Public

activator: EnsureAdminToken's mint path follows a symlink at a flat row's <host>.env Issue open

EnsureAdminToken follows a symlink at a flat-layout row's <host>.env when it mints a token.

  • Where: internal/activator/admintoken.go.
  • The guard has two gaps. The existing-token path is protected by an Lstat check that TestEnsureAdminTokenRefusesToChmodThroughASymlink covers, but the check runs before a separate chmod by path, so it is check-then-act. The mint path has no guard at all. A link planted at <host>.env whose target holds no admin token is appended to and chmodded 0600.
  • Why it matters. It is the same-uid risk spec §7.2 names. The nested layout is safe, because OpenBeneath refuses links.
  • How it was found. The instance-key work (L21, branch instance-key-build, commit ba7c661c) hit the same pattern in its own writer. It fixed it there by refusing the link in the open itself, with O_NOFOLLOW on unix and a build-tagged stub elsewhere. Every instance-key path now has a test. The admin token was left alone, to keep that change in scope.
  • Suggested fix. Route EnsureAdminToken's open through the same O_NOFOLLOW open, on both its mint and existing paths, and add a mint-path test.

Not urgent while the instance key's own path is fixed, but on launch the admin token runs first, so a planted link is still touched there.

A second gap in the same code, found by instance-key Task 3 (f896570c).

All four launch entry points sit behind a dispatcher that refuses fenced and conflicting rows: seedChild, spawnChildFrom, nested prepareAt and the backfill. The trouble is that each launch body then classifies the row a second time.

So if a migration starts between the dispatcher's check and the launch, the row comes back fenced with the flat data directory, and EnsureAdminToken mints into the flat <host>.env of a half-migrated host. Nested prepareAt creates directories first as well.

The instance key now refuses fenced and conflicting rows inside ensureInstanceKeyFor itself, with tests. The clean fix for the admin token is a refusal at the top of each launch body, which would cover both. That would fit with the symlink fix above.

A third case, from instance-key Task 4 review (4a7ee0e6), in the claim launch (claim_snapshot.go:96-110). On a row that is fenced or in conflict mid-wake, the guarded closure mints the admin token into the flat path. Only after that does ensureInstanceKeyFor refuse the row. A layout check at the top of the closure would close this, the same fix as the launch bodies above.

A fourth case, from the instance-key final review (minor 6, instance-key-build a0c906cc): the flat own file's second read. instanceEnvParts reads a flat <host>.env with readEnvFile, which is a plain os.ReadFile. That read comes right after the mint has refused a symlink or FIFO there through openOwnEnvNoFollow, so a link planted between the two is followed. This code predates the branch and the admin token has the same shape. Fixing the token's open would close both if readEnvFile also used openOwnEnvNoFollow, which opens with O_NOFOLLOW and O_NONBLOCK and requires a regular file.