tea login resolution fails open: a seat with no login silently acts as another identity #1356

Closed
opened 2026-08-21 20:33:59 +00:00 by fred · 2 comments
Collaborator

The defect

detect-platform.sh has two credential paths. Only one is identity-aware.

get_gitea_token() (the REST path) resolves an identity from MOSAIC_GIT_IDENTITY
or git config mosaic.gitIdentity, requires a per-slot token at a derived path, and
errors if it is absent. Its own comment says there is no fallback between the seat
and service stores, and it refuses to borrow another slot's token.

get_gitea_login_for_host() (the tea path) has no identity awareness at all. It
returns the first login whose URL matches the host:

login=$(find_tea_login_for_host "$host" || true)

With 43 logins on this host, ~22 match git.mosaicstack.dev. A seat with no login of
its own therefore does not fail — it silently acts as whichever identity happens to be
first in ~/.config/tea/config.yml.

get_gitea_login_for_repo_override() is worse: it falls through to
get_default_tea_login(), which returns the default-marked login, or failing that the
first login of any host.

Why this is worse than an error

Gate 16 depends on author != reviewer. A seat that reviews or merges under a borrowed
identity satisfies the gate mechanically while violating it in fact. Today the failure
happens to be loud, but only by accident: the borrowed default is itself broken. A
working default would make this silent.

Scope

  • Give get_gitea_login_for_host() the same identity ladder the token path has:
    identity -> canonical login <instance>-<seat> -> error if absent. No borrowing.
  • Delete the get_default_tea_login() fallback from
    get_gitea_login_for_repo_override().
  • Leave the identity-unset case alone. That is the operator-at-a-terminal case, and
    the token path does not enforce there either. This finishes the mirror; it does not
    extend it.

Blocker found while scoping

test-gitea-login-resolution.sh is red on any provisioned seat, before any change:

as-is (MOSAIC_GIT_IDENTITY=fred):  rc=1
env -u MOSAIC_GIT_IDENTITY:        rc=0

The suite pins mosaic.gitIdentity empty in git config, but step 0 reads the
environment variable first, and launch-seat.sh exports it for every seat. The
harness comment predicts this failure mode and fixes only the git-config half. The
suite has to be hermetic against the env var too, or it cannot guard this code for any
agent that runs it.

Prerequisite, already done

Every seat token is now projected into tea as <instance>-<seat>
(~/.mosaic/tools/seat-logins/seat-logins.sh; 37 minted, 2 refreshed). Failing closed
before those logins existed would have stopped 22 seats at once.

## The defect `detect-platform.sh` has two credential paths. Only one is identity-aware. `get_gitea_token()` (the REST path) resolves an identity from `MOSAIC_GIT_IDENTITY` or `git config mosaic.gitIdentity`, requires a per-slot token at a derived path, and **errors** if it is absent. Its own comment says there is no fallback between the seat and service stores, and it refuses to borrow another slot's token. `get_gitea_login_for_host()` (the `tea` path) has no identity awareness at all. It returns the **first** login whose URL matches the host: ``` login=$(find_tea_login_for_host "$host" || true) ``` With 43 logins on this host, ~22 match `git.mosaicstack.dev`. A seat with no login of its own therefore does not fail — it silently acts as whichever identity happens to be first in `~/.config/tea/config.yml`. `get_gitea_login_for_repo_override()` is worse: it falls through to `get_default_tea_login()`, which returns the default-marked login, or failing that the first login of *any* host. ## Why this is worse than an error Gate 16 depends on author != reviewer. A seat that reviews or merges under a borrowed identity satisfies the gate mechanically while violating it in fact. Today the failure happens to be loud, but only by accident: the borrowed default is itself broken. A working default would make this silent. ## Scope - Give `get_gitea_login_for_host()` the same identity ladder the token path has: identity -> canonical login `<instance>-<seat>` -> error if absent. No borrowing. - Delete the `get_default_tea_login()` fallback from `get_gitea_login_for_repo_override()`. - Leave the identity-unset case alone. That is the operator-at-a-terminal case, and the token path does not enforce there either. This finishes the mirror; it does not extend it. ## Blocker found while scoping `test-gitea-login-resolution.sh` is red on any provisioned seat, before any change: ``` as-is (MOSAIC_GIT_IDENTITY=fred): rc=1 env -u MOSAIC_GIT_IDENTITY: rc=0 ``` The suite pins `mosaic.gitIdentity` empty in git config, but step 0 reads the **environment variable first**, and `launch-seat.sh` exports it for every seat. The harness comment predicts this failure mode and fixes only the git-config half. The suite has to be hermetic against the env var too, or it cannot guard this code for any agent that runs it. ## Prerequisite, already done Every seat token is now projected into `tea` as `<instance>-<seat>` (`~/.mosaic/tools/seat-logins/seat-logins.sh`; 37 minted, 2 refreshed). Failing closed before those logins existed would have stopped 22 seats at once.
Owner

Initial dev work and testing in the local user dir is fine. However, this tooling MUST end up in the full framework.

Initial dev work and testing in the local user dir is fine. However, this tooling MUST end up in the full framework.
Author
Collaborator

PR #1361 open against next, head 0bf56326, base 24462f46. CI pending. Author is fred; needs an independent reviewer before merge (gate 16). The fleet tooling you asked to land in the framework is in this PR as tools/fleet/seat-logins.sh.

Not in this PR, tracked separately: the onboarding scripts (new-seat.sh, launch-seat.sh, mint-seat-credential.sh) live in ~/.mosaic/fleet/bin/ and are not in the framework at all. Your ruling reads as covering them too; confirming before moving them since that is a larger change.

PR #1361 open against `next`, head `0bf56326`, base `24462f46`. CI pending. Author is fred; needs an independent reviewer before merge (gate 16). The fleet tooling you asked to land in the framework is in this PR as `tools/fleet/seat-logins.sh`. Not in this PR, tracked separately: the onboarding scripts (`new-seat.sh`, `launch-seat.sh`, `mint-seat-credential.sh`) live in `~/.mosaic/fleet/bin/` and are not in the framework at all. Your ruling reads as covering them too; confirming before moving them since that is a larger change.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1356