Repository navigation
Client passwords: a forwarding realm that accepts none guards its own accounts - #882
Merged
Merged
Conversation
… accounts A credential-forwarding realm with `accept_client_passwords: false` was left out of the clients' password chain, so its users' passwords went on to the upstream of a credential-forwarding realm after it. It now keeps its place there as a reject-only guard (`ForwardingRealmGuard`): a name of one of its accounts (`find_upstream_user(username, realm_id)`) is refused as `NotAccepted`, which now ends the chain from any provider, so the password reaches no upstream. Any other name goes on. Fixes #756 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJawoR76E4QRqqppNmL
schmitch
enabled auto-merge
November 3, 2026 22:35
schmitch
disabled auto-merge
November 3, 2026 22:48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #756
What changed
A credential-forwarding realm with
accept_client_passwords: falsewas dropped from the clients' password chain (HTTP Basic on the feeds and/v2/token,npm login --auth-type=legacy). Its users' passwords therefore went on to the upstream of a credential-forwarding realm after it. The sign-in was still refused, but the password had already reached a server that was not the user's realm's.ForwardingRealmGuard(new,crates/core/src/auth/realm_guard.rs): takes the closed realm's place in the client chain. It asks no upstream. For a name the realm owns (find_upstream_user(username, Some(realm_id)), active or not) it returnsAuthOutcome::NotAccepted. Any other name getsSkip, and a failed lookup getsFailed, so the password is not forwarded on a database error either.Chain:AuthOutcome::NotAcceptednow ends the chain whatever provider returns it. Before, only aLocalprovider's did; it was introduced in Client passwords: a closed local realm's refusal no longer counts towards the login lockout #845 for the local realm's reject-only guard. The outcome staysChainOutcome::NotAccepted: the same 401 as a wrong password, and not counted towards the sign-in lockout, since nothing was checked.RealmCache::build(crates/server/src/auth/realms.rs): the client chain keeps every credential-forwarding realm in its place. An accepting realm keeps its provider and a closed one gets its guard. The local provider'sforwarding_followsnow looks only at providers that actually ask an upstream, so a local realm followed only by guards still pays its dummy hash. The browser-login chain is unchanged.docs/deployment.md(Passwords from clients) gets a bullet for the closed forwarding realm. Doc comments inprovider.rs,chain.rsandtoken.rsare updated.Tests
crates/server/tests/client_passwords.rscovers the issue's scenario. Realm order is local, B (corp: forwarding, closed, own mock upstream), then A (nexus: forwarding, accepting).a_closed_forwarding_realms_passwords_never_reach_the_next_realms_upstream: B's user signs NuGet, raw,docker loginandnpm loginin with B's right password and with a wrong one. Every client gets 401, both passwords get the same answers, and neither A's upstream nor B's receives anything. A's own user still gets past B's guard to A. The browser login still accepts B's password, checked at B's upstream only.a_closed_forwarding_realm_guards_its_deactivated_accounts: the same guard holds for a deactivated B account.left: 2, right: 0andleft: 1, right: 0).crates/core/src/auth/chain.rsunit testan_upstream_realms_not_accepted_is_terminal_and_never_forwarded: a non-localNotAcceptedstops the chain, and the next upstream is never consulted.cargo fmt --all --check,cargo clippy -p forge-core -p forge-server --all-targets -- -D warnings,cargo test -p forge-core,cargo test -p forge-server --libplus theadmin,auth_realms,auth_settings,client_passwords,credential_kinds,invitationsandrealm_seedintegration tests, andcargo xtask changelog --check.Changelog
docs/changes/2.2.0/756.security.md(Security): the leak, and that a closed forwarding realm now keeps its accounts' names in the clients' chain.Notes
Not fixed here
docs/deployment.mdis stale after Client passwords: a local realm that accepts none counts each refused client password as a failed sign-in, so a user's old client locks them out of the browser login #795. It still says a closed local realm's refused client password counts towards the lockout ("the lockout counts it"). Since Client passwords: a closed local realm's refusal no longer counts towards the login lockout #845 it does not.Generated by Claude Code