git wrappers: unpinned Gitea identity resolves by first host match, with no tie-break when several logins match #1227

Open
opened 2026-08-15 12:55:22 +00:00 by Ghost · 1 comment

Summary

When no identity is pinned, find_tea_login_for_host() in tools/git/detect-platform.sh picks a Gitea login by host match alone, first hit wins, with no tie-break and no warning that the match was ambiguous.

This is the fallback path, not the primary one — and the primary one is sound. get_gitea_token() honors MOSAIC_GIT_IDENTITY (or per-worktree git config mosaic.gitIdentity), resolves a per-slot token for it, and refuses to fall through to shared credentials when that identity has no token, precisely so a review is never attributed to the wrong agent. That refusal is the right design and this report does not touch it.

The gap is what happens when nothing is pinned.

The selection has no tie-break

for login in logins ...:
    if parsed.hostname == host and name:
        print(name); raise SystemExit(0)

tea permits many logins per host, and having several is normal for anyone with a personal account plus one or more agent seats on the same Gitea. When more than one matches, the winner is decided by the ordering of tea login list — a property of the operator's tea config, not of anything the caller asked for. The caller is not told the set was ambiguous, so an unpinned call can authenticate as a more privileged account than the work called for and report success.

The failure is quiet in both directions: if the first match happens to be the intended one it works for months, and it changes when an unrelated login is added.

Suggested fix

When several logins match a host and none was named by MOSAIC_GIT_IDENTITY, GITEA_LOGIN, or --login, fail with the list rather than picking. An ambiguous identity is the case where guessing is least defensible, and the existing get_gitea_token() behavior already establishes the principle — it refuses to borrow rather than silently substituting.

Secondary: --login surface is inconsistent

Only three wrappers parse a --login flag (issue-comment.sh, pr-edit.sh, pr-review.sh). Others take the environment instead, and some take neither, so the way to pin an identity depends on which wrapper you reached for. Worth making uniform, but it is a surface inconsistency rather than a missing capability: MOSAIC_GIT_IDENTITY reaches the token resolver for all of them.

test-gitea-login-resolution.sh already exercises this resolver, so the harness for an ambiguous-multi-login case exists.

## Summary When no identity is pinned, `find_tea_login_for_host()` in `tools/git/detect-platform.sh` picks a Gitea login by **host match alone, first hit wins**, with no tie-break and no warning that the match was ambiguous. This is the fallback path, not the primary one — and the primary one is sound. `get_gitea_token()` honors `MOSAIC_GIT_IDENTITY` (or per-worktree `git config mosaic.gitIdentity`), resolves a per-slot token for it, and **refuses to fall through to shared credentials** when that identity has no token, precisely so a review is never attributed to the wrong agent. That refusal is the right design and this report does not touch it. The gap is what happens when nothing is pinned. ## The selection has no tie-break ``` for login in logins ...: if parsed.hostname == host and name: print(name); raise SystemExit(0) ``` `tea` permits many logins per host, and having several is normal for anyone with a personal account plus one or more agent seats on the same Gitea. When more than one matches, the winner is decided by the ordering of `tea login list` — a property of the operator's `tea` config, not of anything the caller asked for. The caller is not told the set was ambiguous, so an unpinned call can authenticate as a more privileged account than the work called for and report success. The failure is quiet in both directions: if the first match happens to be the intended one it works for months, and it changes when an unrelated login is added. ## Suggested fix When several logins match a host and none was named by `MOSAIC_GIT_IDENTITY`, `GITEA_LOGIN`, or `--login`, fail with the list rather than picking. An ambiguous identity is the case where guessing is least defensible, and the existing `get_gitea_token()` behavior already establishes the principle — it refuses to borrow rather than silently substituting. ## Secondary: `--login` surface is inconsistent Only three wrappers parse a `--login` flag (`issue-comment.sh`, `pr-edit.sh`, `pr-review.sh`). Others take the environment instead, and some take neither, so the way to pin an identity depends on which wrapper you reached for. Worth making uniform, but it is a surface inconsistency rather than a missing capability: `MOSAIC_GIT_IDENTITY` reaches the token resolver for all of them. `test-gitea-login-resolution.sh` already exercises this resolver, so the harness for an ambiguous-multi-login case exists.

A second, sharper case of the same root cause, measured today: an identity the caller pinned explicitly is silently ignored.

The issue above is about an unpinned call picking a login by first-host-match. But issue-comment.sh, pr-edit.sh and pr-review.sh resolve the acting login from the local tea login list, and MOSAIC_GIT_IDENTITY does not reach that resolution at all. A caller that exports it — the documented way to pin an identity everywhere else, and the mechanism get_gitea_token() honors — gets the host default login instead, with no warning that the pin was inert.

Why that is worse than an ambiguous match: the host default is frequently the operator's own account rather than an agent seat, so the failure mode is not "attributed to the wrong agent" but "attributed to the human owner", typically an admin. An agent that believes it is acting as a non-admin role account can perform the write as the account whose privilege the role separation exists to avoid using. Gate 16 (author ≠ reviewer) rests on exactly this attribution.

Reproduced: with MOSAIC_GIT_IDENTITY=<agent-seat> exported and a valid token present for that seat, issue-comment.sh resolved the host default login instead. It failed safe only by accident — that default login's stored token happened to be stale, so the /user identity read returned 401 and the wrapper refused to post. Had the default's token been live, the comment would have posted as the default identity and reported success. Passing --login <seat> explicitly then worked and verified correctly; the explicit path already fails closed and binds write, identity read and read-back to one credential, which is the right design.

Suggested fix, in preference order:

  1. Have the tea-login resolution consult MOSAIC_GIT_IDENTITY (and git config mosaic.gitIdentity) before falling back to the host default, so one documented pin means the same thing across every wrapper.
  2. Failing that, when MOSAIC_GIT_IDENTITY is set and the wrapper is about to ignore it, fail rather than proceed — a pin that is silently dropped is worse than no pin, because the caller has already reasoned about attribution and concluded it was handled.

Both are the same principle the --login path and get_gitea_token() already apply: refuse to substitute an identity the caller did not ask for.

A second, sharper case of the same root cause, measured today: **an identity the caller pinned explicitly is silently ignored.** The issue above is about an *unpinned* call picking a login by first-host-match. But `issue-comment.sh`, `pr-edit.sh` and `pr-review.sh` resolve the acting login from the local `tea` login list, and **`MOSAIC_GIT_IDENTITY` does not reach that resolution at all**. A caller that exports it — the documented way to pin an identity everywhere else, and the mechanism `get_gitea_token()` honors — gets the **host default** login instead, with no warning that the pin was inert. Why that is worse than an ambiguous match: the host default is frequently the operator's own account rather than an agent seat, so the failure mode is not "attributed to the wrong agent" but "attributed to the human owner", typically an admin. An agent that believes it is acting as a non-admin role account can perform the write as the account whose privilege the role separation exists to avoid using. Gate 16 (author ≠ reviewer) rests on exactly this attribution. Reproduced: with `MOSAIC_GIT_IDENTITY=<agent-seat>` exported and a valid token present for that seat, `issue-comment.sh` resolved the host default login instead. It failed safe only by accident — that default login's stored token happened to be stale, so the `/user` identity read returned 401 and the wrapper refused to post. **Had the default's token been live, the comment would have posted as the default identity and reported success.** Passing `--login <seat>` explicitly then worked and verified correctly; the explicit path already fails closed and binds write, identity read and read-back to one credential, which is the right design. Suggested fix, in preference order: 1. Have the `tea`-login resolution consult `MOSAIC_GIT_IDENTITY` (and `git config mosaic.gitIdentity`) before falling back to the host default, so one documented pin means the same thing across every wrapper. 2. Failing that, when `MOSAIC_GIT_IDENTITY` is set and the wrapper is about to ignore it, **fail rather than proceed** — a pin that is silently dropped is worse than no pin, because the caller has already reasoned about attribution and concluded it was handled. Both are the same principle the `--login` path and `get_gitea_token()` already apply: refuse to substitute an identity the caller did not ask for.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1227