get_gitea_login_for_host matches tea login by NAME, so a seat present on both hosts silently authors as the shared account (distinct from #1044) #1188

Open
opened 2026-08-13 01:12:00 +00:00 by Mos · 0 comments
Contributor

Summary

get_gitea_login_for_host resolves a seat's identity to a tea login by comparing the identity string to the login's NAME. tea's login namespace is flat and global, but identity is per-host. A seat that exists on both git.uscllc.com and git.mosaicstack.dev can therefore only ever be name-matched on one of them — on the other it falls through to first-host-match, which on mosaicstack is the shared Mos account.

This is distinct from #1044 and should not be merged into it. #1044 is get_gitea_token failing open on an unset identity. This defect fires with the identity explicitly set and a valid per-slot token present, because pr-create.sh's primary path is tea pr create --login "$GITEA_LOGIN_NAME" and never reaches the token resolver at all.

Measured, with positive control and a negative result that is the actual finding

Same host, same wrapper, four live fleet seats, MOSAIC_GIT_IDENTITY set on every call:

identity resolves to (identity only) with GITEA_LOGIN
coder2 mosaicstack-mos@Mos mosaicstack-coder2
coder3 mosaicstack-mos@Mos mosaicstack-coder3
be-coder-06 be-coder-06 unchanged
be-coder-08 be-coder-08 unchanged

be-coder-06 / be-coder-08 are the positive control: the wrapper attributes correctly when a login's NAME happens to equal the identity, so the fault is not "attribution never works."

Field instances: PR #1186 (coder2) and PR #1173 (coder3) were both created with the identity set correctly and both carry @Mos as the PR-object author.

The negative result is the finding. I registered mosaicstack logins for both seats — tea logins add verified against /user and returned "Login as coder2 on https://git.mosaicstack.dev successful" — and they still resolve to Mos. Registration could not fix them, because the names coder2 and coder3 were already taken by their git.uscllc.com logins, so the new logins had to be mosaicstack-coder2 / mosaicstack-coder3, which a name-only matcher does not recognise.

This corrects the staged-precondition comment in detect-platform.sh

The comment stages the mosaicstack fail-closed refusal behind a read:user scope grant, naming coder-mos1 / coder-mos2 / f10-coder / merge-gate as unregisterable. For the seats that actually failed, that was never the blocker: coder2 and coder3 verify against /user fine. Their blocker is the flat namespace.

To be explicit, so the two justifications stay unconflated exactly as that comment asks: this issue is not a request for read:user. It is the opposite — it shows that for these seats the scope was never the obstacle.

mosaicstack-rev-974 (user rev-974) already exists in the login list and is a latent instance of the same defect: identity rev-974 acting on mosaicstack will not match that login's name and will resolve to Mos today.

Proposed fix

  1. Match on the login's user field, not its name (name as fallback), in both tea_login_matches_host and the identity branch of get_gitea_login_for_host. mosaicstack-coder2 has user: coder2; matching on user resolves it with no rename and no env override, and fixes mosaicstack-rev-974 at the same time.
  2. Return the matched login's NAME, not the identity. The identity branch currently echo "$_mgi", which is only correct while name == identity. That coupling is what forced the flat-namespace constraint in the first place; tea --login needs the login name.
  3. Then enable the mosaicstack refusal. With (1) and (2) the documented ORDER — "register the missing tea logins FIRST, re-measure, THEN enable the refusal" — is satisfiable for every live mosaicstack-acting seat, and the fall-through can be removed rather than left as a silent default.

Interim mitigation in force

Affected seats now prefix GITEA_LOGIN=mosaicstack-<seat> alongside MOSAIC_GIT_IDENTITY=<seat> on every authoring call. GITEA_LOGIN is honoured first and is still verified against the host, so it is an explicit correction rather than a bypass. It is a mitigation for two known seats and closes nothing: the next seat provisioned onto mosaicstack with a name collision impersonates the shared account silently, exactly as these two did.

Note for whoever implements this

tea logins add --token also reads $GITEA_SERVER_TOKEN from the environment. Register logins that way — putting the token on --token places a credential on argv, which is visible in the process table on hosts without hidepid.

## Summary `get_gitea_login_for_host` resolves a seat's identity to a `tea` login by comparing the identity string to the login's **NAME**. `tea`'s login namespace is **flat and global**, but identity is **per-host**. A seat that exists on both `git.uscllc.com` and `git.mosaicstack.dev` can therefore only ever be name-matched on **one** of them — on the other it falls through to first-host-match, which on mosaicstack is the shared `Mos` account. This is **distinct from #1044** and should not be merged into it. #1044 is `get_gitea_token` failing open on an **unset** identity. This defect fires with the identity **explicitly set** and a **valid per-slot token present**, because `pr-create.sh`'s primary path is `tea pr create --login "$GITEA_LOGIN_NAME"` and never reaches the token resolver at all. ## Measured, with positive control and a negative result that is the actual finding Same host, same wrapper, four live fleet seats, `MOSAIC_GIT_IDENTITY` set on every call: | identity | resolves to (identity only) | with `GITEA_LOGIN` | |---|---|---| | `coder2` | `mosaicstack-mos` → **@Mos** | `mosaicstack-coder2` ✅ | | `coder3` | `mosaicstack-mos` → **@Mos** | `mosaicstack-coder3` ✅ | | `be-coder-06` | `be-coder-06` ✅ | unchanged | | `be-coder-08` | `be-coder-08` ✅ | unchanged | `be-coder-06` / `be-coder-08` are the **positive control**: the wrapper attributes correctly when a login's NAME happens to equal the identity, so the fault is not "attribution never works." Field instances: **PR #1186** (`coder2`) and **PR #1173** (`coder3`) were both created with the identity set correctly and both carry `@Mos` as the PR-object author. **The negative result is the finding.** I registered mosaicstack logins for both seats — `tea logins add` verified against `/user` and returned *"Login as coder2 on https://git.mosaicstack.dev successful"* — and they **still** resolve to `Mos`. Registration could not fix them, because the names `coder2` and `coder3` were already taken by their `git.uscllc.com` logins, so the new logins had to be `mosaicstack-coder2` / `mosaicstack-coder3`, which a name-only matcher does not recognise. ## This corrects the staged-precondition comment in `detect-platform.sh` The comment stages the mosaicstack fail-closed refusal behind a `read:user` scope grant, naming `coder-mos1` / `coder-mos2` / `f10-coder` / `merge-gate` as unregisterable. For the seats that actually failed, that was **never** the blocker: `coder2` and `coder3` verify against `/user` fine. Their blocker is the flat namespace. To be explicit, so the two justifications stay unconflated exactly as that comment asks: **this issue is not a request for `read:user`.** It is the opposite — it shows that for these seats the scope was never the obstacle. `mosaicstack-rev-974` (user `rev-974`) already exists in the login list and is a **latent instance of the same defect**: identity `rev-974` acting on mosaicstack will not match that login's name and will resolve to `Mos` today. ## Proposed fix 1. **Match on the login's `user` field, not its name** (name as fallback), in both `tea_login_matches_host` and the identity branch of `get_gitea_login_for_host`. `mosaicstack-coder2` has `user: coder2`; matching on `user` resolves it with no rename and no env override, and fixes `mosaicstack-rev-974` at the same time. 2. **Return the matched login's NAME, not the identity.** The identity branch currently `echo "$_mgi"`, which is only correct while name == identity. That coupling is what forced the flat-namespace constraint in the first place; `tea --login` needs the login name. 3. **Then enable the mosaicstack refusal.** With (1) and (2) the documented ORDER — *"register the missing tea logins FIRST, re-measure, THEN enable the refusal"* — is satisfiable for every live mosaicstack-acting seat, and the fall-through can be removed rather than left as a silent default. ## Interim mitigation in force Affected seats now prefix `GITEA_LOGIN=mosaicstack-<seat>` alongside `MOSAIC_GIT_IDENTITY=<seat>` on every authoring call. `GITEA_LOGIN` is honoured first and is still verified against the host, so it is an explicit correction rather than a bypass. It is a mitigation for two known seats and closes nothing: **the next seat provisioned onto mosaicstack with a name collision impersonates the shared account silently, exactly as these two did.** ## Note for whoever implements this `tea logins add --token` also reads `$GITEA_SERVER_TOKEN` from the environment. Register logins that way — putting the token on `--token` places a credential on argv, which is visible in the process table on hosts without `hidepid`.
Mos added the bug label 2026-08-13 01:12:00 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1188