git wrappers: no --login resolves a GUESSED shared login instead of the caller's own credential (gate-16 attribution defect) #1351

Closed
opened 2026-08-21 03:53:36 +00:00 by fred · 0 comments
Collaborator

What is broken

pr-review.sh and issue-comment.sh, when called with no --login, do not use the
caller's own credential. They GUESS a login from the repo host
(get_gitea_login_for_host / get_gitea_login), then look that guessed name up in
~/.config/tea/config.yml via get_gitea_token_for_login.

On git.mosaicstack.dev the guess resolves to mosaicstack-mos-dt-0, a shared account.

get_gitea_token_for_login performs no authentication check anywhere in its body. It
matches by login NAME and returns the token string. So it returns rc=0 for a dead
credential, the || get_gitea_token "$host" fallback never fires, and the identity-aware
resolver is unreachable on this path.

Measurement

On git.mosaicstack.dev, with MOSAIC_GIT_IDENTITY set:

call rc token authenticates as
get_gitea_token_for_login <guessed> 0 HTTP 401
get_gitea_token <host> 0 HTTP 200, login fred, id 36

Control: the same endpoint with no credential returns 401, so the 401 above is reachable
and the 200 is not a default.

The defect is attribution, not liveness

The dead token is what made this visible; it is not the bug.

Had the shared token been alive, every seat's reviews and comments would have posted as
mosaicstack-mos-dt-0. Constitution gate 16 (author is not reviewer) would then be
silently unenforceable fleet-wide: every review would carry the same author, and no
read-back would notice, because the read-back compares against the credential's own login.

A caller passing no --login is asking to act as itself. The guess answered a
question nobody asked.

Scope

Two wrappers reach the tea store through the guessed path: pr-review.sh (3 call sites)
and issue-comment.sh (1). pr-edit.sh also calls get_gitea_token_for_login, on its
explicit---login path, which is correct and unaffected.

Fix

PR removes the guess and its tea lookup from the no---login path in both wrappers. That
path resolves the acting identity's own credential via get_gitea_token, which fails loud
on a fleet host when no identity resolves. That refusal is the correct outcome and is
deliberately not fallen back from.

get_gitea_token_for_login the function is unchanged, along with the 53 assert_token
pins in test-gitea-login-resolution.sh. --login remains the only route to the tea store.

Host-side rollout, separate from the code fix

The code change is a no-op wherever MOSAIC_GIT_IDENTITY is unset, because
get_gitea_token then fails and the wrapper correctly refuses. start-agent-session.sh
on next already validates that the variable exists and equals AGENT_NAME
(git-identity-mismatch), so this is already framework law and needs no new
convention. Hosts running a framework install that predates that validator need a
framework upgrade and a seat-env regeneration, not hand-edited launch envs.

On sb-it-1-dt: 17 of 19 live tmux sessions have a readable
fleet/agents/<seat>/secrets/gitea-mosaicstack-<seat>.token. The two without are junk
(scratch) and vision-unslop (a second session for the vision seat) — that slot needs
provisioning or the session needs to be retired before the rollout.

Related

detect-platform.sh states the rule the tea/seat duplication breaks, in its own comment:
"One credential, one location: two copies diverge, and the stale copy fails in a way that
reads as a revoked token rather than as drift."
The tea fred-ms token and
fleet/agents/fred/secrets/gitea-mosaicstack-fred.token are two different credentials for
the same identity (fred, id 36).

Diagnosis by fred, adversarial review by fargo.

## What is broken `pr-review.sh` and `issue-comment.sh`, when called with **no `--login`**, do not use the caller's own credential. They GUESS a login from the repo host (`get_gitea_login_for_host` / `get_gitea_login`), then look that guessed name up in `~/.config/tea/config.yml` via `get_gitea_token_for_login`. On `git.mosaicstack.dev` the guess resolves to `mosaicstack-mos-dt-0`, a **shared account**. `get_gitea_token_for_login` performs **no authentication check anywhere in its body**. It matches by login NAME and returns the token string. So it returns rc=0 for a dead credential, the `|| get_gitea_token "$host"` fallback never fires, and the identity-aware resolver is unreachable on this path. ## Measurement On `git.mosaicstack.dev`, with `MOSAIC_GIT_IDENTITY` set: | call | rc | token authenticates as | |---|---|---| | `get_gitea_token_for_login <guessed>` | 0 | **HTTP 401** | | `get_gitea_token <host>` | 0 | **HTTP 200**, login `fred`, id 36 | Control: the same endpoint with no credential returns 401, so the 401 above is reachable and the 200 is not a default. ## The defect is attribution, not liveness The dead token is what made this **visible**; it is not the bug. Had the shared token been alive, every seat's reviews and comments would have posted as `mosaicstack-mos-dt-0`. Constitution gate 16 (author is not reviewer) would then be **silently unenforceable fleet-wide**: every review would carry the same author, and no read-back would notice, because the read-back compares against the credential's own login. A caller passing no `--login` is asking to act as **itself**. The guess answered a question nobody asked. ## Scope Two wrappers reach the tea store through the guessed path: `pr-review.sh` (3 call sites) and `issue-comment.sh` (1). `pr-edit.sh` also calls `get_gitea_token_for_login`, on its explicit-`--login` path, which is correct and unaffected. ## Fix PR removes the guess and its tea lookup from the no-`--login` path in both wrappers. That path resolves the acting identity's own credential via `get_gitea_token`, which fails loud on a fleet host when no identity resolves. That refusal is the correct outcome and is deliberately not fallen back from. `get_gitea_token_for_login` the function is unchanged, along with the 53 `assert_token` pins in `test-gitea-login-resolution.sh`. `--login` remains the only route to the tea store. ## Host-side rollout, separate from the code fix The code change is a no-op wherever `MOSAIC_GIT_IDENTITY` is unset, because `get_gitea_token` then fails and the wrapper correctly refuses. `start-agent-session.sh` on `next` already validates that the variable exists and equals `AGENT_NAME` (`git-identity-mismatch`), so this is **already framework law** and needs no new convention. Hosts running a framework install that predates that validator need a framework upgrade and a seat-env regeneration, not hand-edited launch envs. On `sb-it-1-dt`: 17 of 19 live tmux sessions have a readable `fleet/agents/<seat>/secrets/gitea-mosaicstack-<seat>.token`. The two without are `junk` (scratch) and `vision-unslop` (a second session for the `vision` seat) — that slot needs provisioning or the session needs to be retired before the rollout. ## Related `detect-platform.sh` states the rule the tea/seat duplication breaks, in its own comment: *"One credential, one location: two copies diverge, and the stale copy fails in a way that reads as a revoked token rather than as drift."* The tea `fred-ms` token and `fleet/agents/fred/secrets/gitea-mosaicstack-fred.token` are two different credentials for the same identity (`fred`, id 36). Diagnosis by `fred`, adversarial review by `fargo`.
fred closed this issue 2026-08-21 04:10:44 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1351