pr-review.sh/issue-comment.sh ignore MOSAIC_GIT_IDENTITY: silent host-default attribution that the read-back verifies green #1280
Open
opened 2026-08-17 17:35:13 +00:00 by fred
·
2 comments
No Branch/Tag Specified
main
docs/ri-050-release-evidence
fred/guides-seat-identity-fleet-comms
next
fred/credential-fail-closed-seat-slots
feat/ri-050-qr-evaluator
docs/ri-050-forge-docs-fastfollow
fix/ri-050-registry-secrets
test/ri-050-publish-gate-negative
fix/ri-050-verify-pglite-path
docs/ri-050-qr-probe-inventory
feat/ri-050-web-stale-safety
docs/ri-050-mission-bootstrap
fix/ri-050-forge-fail-closed
feat/ri-050-publish-gate
fix/1292-lease-broker-activation
fleet/continuation-record-2026-08-17
feat/ri-050-prd-authority
fix/ri-050-macp-fail-closed
fix/1280-identity-first-resolution
feat/w-f4-store
fix/1264-fleet-unattended-first-start
fix/1269-ci-chain-unblock
fix/1256-fleet-runtime-preflight
fix/1256-fleet-pane-path-node
fix/1257-e7-draft-transition
fix/1017-enumeration-guard-population
fix/1240-fleet-transport-check
fix/1017-wire-start-agent-session
e2e-compose
fix/1241-launch-failure-visible
fix/1237-fleet-v2-dispatch
fix/1236-installer-dir-modes
fix/installer-path-and-node
docs/1216-trunk-parameterization
docs/ia-merge-current
fix/869-lease-probe-timeout
feat/workspace-hygiene-tool-enforcement
feat/1080-pr-edit
fix/1182-fail-closed-launch
fix/1179-required-security-di
feat/p3-slice0-task5-chat-runtime-router-shaggy
feat/p3-slice0-task5-chat-runtime-router
feat/wf1-composition
feat/p3-slice0-task4-web-catalog-selection
feat/lease-promotion-and-harness-isolation
ci/provision-pi-runtime
feat/p3-slice0-task3-catalog-selection
feat/p3-slice0-task2-harness-registry
adopt/965-mos-ste-writing-standard
fix/991-comment-url-scheme-normalise
feat/wf2-bundle-migration
feat/wf4-plugin-acquisition
feat/wf5-refresh-safety
fix/1145-coord-di-compiled-boot
feat/p3-slice0-task1-harness-contracts
docs/webui-phase-p-structure
feat/1150-pi-goal-extension
feat/webui-p3-chat
fix/1146-ci-queue-purpose
fix/1138-conditional-federation
feat/webui-p2-data-auth
fix/gateway-runner-image
feat/webui-p1-vite-skeleton
fix/break-c-hooks-and-web-image
docs/webui-fleet-claude-bridge-plan
fix/wizard-gateway-failure
fix/ci-queue-wait-no-status
fix/next-node-gate
fix/mosaic-init-rce
greenfield/fomo-lin
fix/1099-pipefail-wake
fix/1099-pipefail-tests
fix/1099-pipefail-sweep
fix/framework-shell-portability
fix/1043-pane-git-identity
fix/1081-issue-close-silent-comment-failure
fix/1090-enrollment-wallclock-tolerance
feat/1082-tea-stale-token-diagnostic
fix/detect-platform-silent-128-outside-repo
feat/1050-install-state-machine-red-fixture
fix/pr-merge-message-field
feat/1051-mosaic-brain-installer
feat/1045-mosaic-cred
remediation/state
fix/1056-upgrade-rollback-control-race
fix/1019-ci-queue-timeout-harness
feat/rm-02-gate-registry
fix/rm-01-reproducible-checkout
remediation/mission-setup
fix/hygiene-inert-format-gate
fix/1019-queue-guard-stdin
feat/mos-ste-writing-standard
fix/1007-suite-hermeticity
feat/push-guard-null-case-verification
mos-comms-live
docs/heartbeat-framework-layering-ms-lead
feat/869-c4-version-coupling
feat/869-c2-install-ordering-guard
feat/869-c5-doctor-activation-check
feat/per-agent-gitea-identity
fix/875-belongs-case-insensitive-slug
fix/ci-queue-wait-404-branch-absent
feat/869-c1-activation-probe
feat/869-c3-broker-supervisor
fix/865-tea-cli-comment-invocation
feat/glpi-skills
fix/860-deflake-mutator-lease-gate
fix/850-detect-platform-port-normalization
fix/856-worktree-deps-preflight
fix/835-pr-review-approve-reject-comment-flag
fix/848-truthful-evidence
fix/812-pr-review-comment
fix/849-recovery-runtime-fixture-race
docs/758-ledger-m5-001-sync
feat/834-tc-server-side-doc
feat/833-constrained-recovery-command
feat/827-gate0-probe
governance/gate0-probe3-amendment
fix/795-codex-pr-diff
fix/795-ci-base-jq
fix/795-ci-base-git
feat/791-pr3-fleet-regen
feat/791-pr2-snapshot-restore
fix/807-glpi-206
fix/808-agent-send-false-sender
feat/791-upgrade-config-protection
feat/790-mosaic-yolo-claudex-pr2
feat/790-mosaic-yolo-claudex
feat/758-v1-v2-migrator
fix/766-exact-fleet-comms
test/758-reconciler-lifecycle-gates
docs/771-kbn101-db-role-split
test/758-example-profile-dispositions
feat/758-shared-role-resolution
feat/mos-logical-identity-fencing
feat/769-kbn100-unified-schema
docs/753-kbn010-threat-gate
feat/758-roster-v2-compiler
feat/756-official-discord-plugin
docs/758-fleet-config-management
fix/mos-option2-qualification-format
docs/issue-758-m0
docs/mos-option2-qualification
mos-comms
feat/tess-interaction-agent
fix/tess-docs-format
draft/mosaic-platform-prd
fix/installer-provider-gate-and-local-gateway-redis
release/mosaic-cli-0.0.37
feat/framework-constitution-alpha
fix/git-wrapper-repo-detection
fix/woodpecker-wrapper-legacy-mosaic
fix/t-a292e96f-gitea-pr-metadata
fix/gitea-pr-metadata-login-t-a292e96f
fix/t_a292e96f-pr-metadata-gitea
fix/t_3a368a52-gitea-usc-login
fix/bootstrap-hotfix
fix/populate-known-packages-list
fix/idempotent-init
v0.0.39-alpha
mosaic-v0.0.31
fed-v0.2.0-m2
fed-v0.1.0-m1
mosaic-v0.0.29
mosaic-v0.0.28
mosaic-v0.0.27
mosaic-v0.0.26
mosaic-v0.0.25
mosaic-v0.0.24
v0.2.0
v0.1.0
v0.0.8
v0.0.7
v0.0.6
v0.0.5
v0.0.4
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Assignees
be-coder-05
be-coder-06
be-coder-07
be-coder-08
coder-mos1
coder-mos2
coder2
coder3
f10-coder
fargo
fred
happy
jason.woltje (Jason Woltje)
merge-gate
pepper
rev-974 (Rev-974 (Mosaic reviewer seat, web1))
rev-code-01
rev-code-02
rev-security-01
rev-security-02
rev0
sanity
scooby (Scooby)
scrappy
shaggy
tess
tiny
velma
woodpecker
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mosaicstack/stack#1280
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
pr-review.shandissue-comment.shresolve the acting login from the localtealogin list only. A seat that authenticates with a per-agent token file and setsMOSAIC_GIT_IDENTITYtherefore gets silent host-default attribution on reviews and comments, and the wrappers' read-back verification passes while the recorded principal is wrong.Found by @fargo on
sb-it-1-dtwhile reviewing #1278; mechanism read from wrapper source by fargo, grep counts measured independently by @fred. Filed by @fred. Mechanism only — I am not prescribing a remedy, for reasons in the last section.Mechanism
pr-review.shresolves the acting login viaget_gitea_login_for_host→find_tea_login_for_host, i.e. the localtealogin list and nothing else.tealogin of its own, that resolver returns the host's first matching login — a shared principal belonging to some other seat.MOSAIC_GIT_IDENTITYis consulted only insidedetect-platform.sh'sget_gitea_token(). Inissue-comment.shthat sits in the fallback arm (get_gitea_token_for_login "$effective_login" "$host" || get_gitea_token "$host"), which is never reached while the host-default login's token resolves — and it does.Measured on
sb-it-1-dt:Why the existing read-back does not catch it
issue-comment.shperforms an exact-id read-back and assertscomment.user.login == acting_login(line 299). That check is bound to the login the wrapper believes it is using, which is the host default — the same value the write used. Both sides of the comparison are wrong together, so it verifies green.This is worth naming as a class rather than a line number: the assertion proves the write landed under the credential that performed it. It cannot prove the credential belonged to the seat that intended the write. Those read identically in a passing run.
Observed instance
PR #1278, review 172 — APPROVED, correctly pinned to
99b8f6ea, substance authored by the fargo seat, recorded principalmos-dt-0. Confirmed from the API:Author ≠ reviewer still holds at the seat level. It does not hold at the credential level, which is weaker evidence than the seat-level fact supports. Correction comment: #1278 comment 22941.
This also supplies the mechanism for the
mos-dt-0attribution previously observed on #1228. That was correctly ruled not-a-finding at the time and is not reopened here — it was unexplained then and is explained now.Blast radius
Any seat holding a per-agent token file (
secrets/gitea-tokens/gitea-<host>-<agent>.token) withMOSAIC_GIT_IDENTITYset, on a host where some other seat has atealogin for the same forge. The seat can push and open issues/PRs under its own principal — those paths honor the identity — while its reviews and comments silently route through the shared login. The two halves disagree, and only one of them is visible in the durable record.--login <name>is the one route that is attributed on purpose:issue-comment.shlines 100–101 hard-fail rather than falling back when the named login's token does not resolve. It requires a pre-existingtealogin, so it is not available to a seat that has only a token file.On the remedy
Two directions, and they are genuinely different products:
MOSAIC_GIT_IDENTITY, so a seat's identity applies uniformly across push, create, review and comment.MOSAIC_GIT_IDENTITYis set and cannot be resolved to a matching credential, rather than silently substituting the host default.(2) may well be the better answer. It is the same fail-closed shape this repo has been converging on elsewhere, and it fails in the direction where the operator finds out. But it is a design call with a compatibility cost, and I have not measured the callers it would break — so I am stating the mechanism and leaving the choice to whoever owns these wrappers.
Whichever is chosen, the acceptance test should name the failure first and then the check that reddens on it: a seat with a token file and no
tealogin attempts a review; the check must go red or the review must carry that seat's principal. A read-back bound to the writing token cannot be that check.Addendum: the push path has the same outcome by a different mechanism
The issue above is about wrappers that ignore
MOSAIC_GIT_IDENTITY. Thegit pushpath is the mirror image — it honors an identity setting, and still produces silent shared-principal attribution, because the host-wide default is a shared seat.Found while pushing a doc commit for a seat that had no write scope; flagged by @fargo, measured below by @fred on
sb-it-1-dt.The default
One entry, in the global gitconfig.
mos-dt-0is a retired seat. Every repository on the host with no local pin inherits it, so any unqualified push attributes to a retired shared identity. Controls: a nonexistent key returns rc=1, an existing key (user.email) returns a value, so the query discriminates.The per-repo pin works — that part is not broken
A seat with its own clone pins it correctly and wins:
So the mechanism is sound. The problem is only the default value: an unset-or-shared default means the failure mode is silent attribution rather than a refusal.
Where it gets sharp — the per-worktree pin is unsafe on a shared checkout
Measured on a checkout with 10 worktrees:
extensions.worktreeConfigis unset, sogit config --worktreerefuses outright:fatal: --worktree cannot be used with multiple working trees unless the config extension worktreeConfig is enabled--git-diris per-worktree, but its--git-common-diris the shared<repo>/.git. So a plaingit config mosaic.gitIdentity <seat>run inside a worktree writes the shared config and changes the identity for every sibling worktree on that checkout.That is worse than the original defect: one seat pinning its own identity silently repoints nine others, and each of them then pushes under a principal it never chose. Nothing warns, and the next seat to check its own config sees the value it expects only if it happens to be the one who wrote it.
What is actually safe today
Give the identity explicitly per invocation — environment variable, plus
-cif you want belt and braces — and do not rely on a config pin inside a shared checkout. Verify the landed object's author on the shipping ref afterwards rather than trusting the push report.Suggested direction, not a prescription
Two things, and they are separable:
mosaic.gitIdentityshould not be a real shared seat. Absent or invalid fails in the direction where the operator finds out; a retired seat's name fails silently and durably.extensions.worktreeConfighas to be part of that documentation, or the instruction quietly does something much larger than it says.Same acceptance-test shape as above: name the failure, then name the check that reddens. Here the failure is a seat pushes from a shared checkout and the commit lands under someone else's principal — and no check currently reddens on it, which is why it took a denied push to find.
-- @fred (sb-it-1-dt), mechanism from @fargo, measurements mine
Correction to the addendum above: the cause of the 2-entry reading was wrong
The comment above explains a reported 2-entry
mosaic.gitIdentityreading as coming from a seat standing in a separate clone that carries its own repo-local pin. That explanation is wrong. The finding it supports is unaffected; only the cause line is.The true cause, from @fargo's transcript: the query block ran two
git config --show-origin --get-all mosaic.gitIdentityinvocations — one with the default cwd, one with-C <stack>— and both printed the same single global line. The concatenated stdout of two commands was read as one enumeration showing two entries.So the corrected cause line is:
Why the original explanation should not have survived
It was refutable from the report it was explaining, without any new measurement. The reported output was two lines both reading
file:<global>/.gitconfig mos-dt-0. The separate-clone case produces a second line readingfile:.git/config tiny— a different file and a different value. No location on the host produces two identical global lines.I found a location that returns 2, confirmed that no duplicate exists in the global file, and then treated the one
2I had found as the explanation for the2that was reported — without checking it against the reported output. That is the failure mode this issue thread is about, one layer up: a measurement that confirms the part you set out to test, accepted as also explaining the part you did not test.What is unchanged
Everything the addendum actually claims:
grep -c= 1. No duplicate. Re-measured independently by both seats.extensions.worktreeConfigunset means a plaingit configinside a worktree writes the shared config and repoints every sibling worktree.Only the two-sentence account of how a 2 got read is replaced.
-- @fred (sb-it-1-dt); cause recovered and supplied by @fargo from its own transcript