Follow-up from PR #564 (round-12 review). Not a security hole — a usability and correctness wart during the migration window only.
What happens
#564 stopped a farm-B logout from revoking a farm-A legacy session. It did not stop it deleting the cookie.
src/Cluckwork.Api/Endpoints/Auth/AuthEndpoints.cs:618-619 clears the legacy cookie unconditionally whenever one is presented, regardless of which farm owns it. So during the migration window:
- a tab opened before the deploy holds legacy
cluckwork_rt for farm A;
- a new login writes
cluckwork_rt_<B>;
- logging out of farm B deletes both cookies.
Farm A's tab is signed out by a logout it had nothing to do with. The same path is reachable from revokeSupersededCookie, which the user never initiates.
Worse in a quiet way: farm A's refresh token is now live but unreachable — not revoked, so it survives to ExpiresAt, but its only browser copy is gone. Nothing can use it and nothing will clean it up early.
The test pins the loss
tests/Cluckwork.Api.IntegrationTests/RefreshAccountBindingTests.cs:495 asserts that deletion, inside a test named Logout_WithSelectedFarmAndCrossFarmLegacyCookie_RevokesSelectedAndLeavesLegacySessionLive. The name says the session lives; the assertion requires its cookie to be destroyed. Whatever is decided here, that test needs renaming or re-pointing.
Why it was not fixed in #564
The revocation half was the security-relevant half and is fixed and mutation-proven. This half costs a re-login for a user who had two farms open across a deploy boundary, and the legacy branch drains within one refresh-token lifetime. It did not justify holding an eleven-round PR.
Suggested fix
Resolve the legacy cookie's owner before clearing it — the same resolution the revoke path already does — and clear it only when it matches the selected farm, keeping unconditional clearing when only a legacy cookie is presented.
Verify
- a farm-B logout with a farm-A legacy cookie present leaves farm A's cookie and its session usable;
- a logout presenting only a legacy cookie still clears and revokes it;
- the cross-farm test name and assertions agree.
Found by an internal mutation reviewer, round 12 of #564. Epic #530.
Follow-up from PR #564 (round-12 review). Not a security hole — a usability and correctness wart during the migration window only.
What happens
#564 stopped a farm-B logout from revoking a farm-A legacy session. It did not stop it deleting the cookie.
src/Cluckwork.Api/Endpoints/Auth/AuthEndpoints.cs:618-619clears the legacy cookie unconditionally whenever one is presented, regardless of which farm owns it. So during the migration window:cluckwork_rtfor farm A;cluckwork_rt_<B>;Farm A's tab is signed out by a logout it had nothing to do with. The same path is reachable from
revokeSupersededCookie, which the user never initiates.Worse in a quiet way: farm A's refresh token is now live but unreachable — not revoked, so it survives to
ExpiresAt, but its only browser copy is gone. Nothing can use it and nothing will clean it up early.The test pins the loss
tests/Cluckwork.Api.IntegrationTests/RefreshAccountBindingTests.cs:495asserts that deletion, inside a test namedLogout_WithSelectedFarmAndCrossFarmLegacyCookie_RevokesSelectedAndLeavesLegacySessionLive. The name says the session lives; the assertion requires its cookie to be destroyed. Whatever is decided here, that test needs renaming or re-pointing.Why it was not fixed in #564
The revocation half was the security-relevant half and is fixed and mutation-proven. This half costs a re-login for a user who had two farms open across a deploy boundary, and the legacy branch drains within one refresh-token lifetime. It did not justify holding an eleven-round PR.
Suggested fix
Resolve the legacy cookie's owner before clearing it — the same resolution the revoke path already does — and clear it only when it matches the selected farm, keeping unconditional clearing when only a legacy cookie is presented.
Verify
Found by an internal mutation reviewer, round 12 of #564. Epic #530.