diff --git a/core/archipelago/src/api/rpc/middleware.rs b/core/archipelago/src/api/rpc/middleware.rs index 1fafef61..3f36f818 100644 --- a/core/archipelago/src/api/rpc/middleware.rs +++ b/core/archipelago/src/api/rpc/middleware.rs @@ -58,6 +58,18 @@ pub(super) fn sanitize_error_message(msg: &str) -> String { "must be", "cannot", "Password", + // The federation escalation sentinel. "Password" above does NOT cover + // it — starts_with is case-sensitive and the sentinel is ALL-CAPS — + // so the frontend's isPasswordRequired() never saw it and the + // password prompt could never open. Net effect: no node could mint a + // Trusted invite or promote a peer from the trust dropdown, fleet-wide + // (2026-08-09). The sentinel is machine-read by the UI; it must pass + // through verbatim. + "PASSWORD_REQUIRED", + // "Tor address not available. Tor may not be running." — the invite + // handler's precondition, entirely user-actionable, was likewise + // collapsing into the generic message. + "Tor address not available", "Session", "Failed to pull", "Failed to start", @@ -171,6 +183,24 @@ pub(super) fn sanitize_error_message(msg: &str) -> String { mod sanitize_tests { use super::sanitize_error_message; + #[test] + fn password_required_sentinel_passes_through_verbatim() { + // The UI machine-reads this sentinel (isPasswordRequired checks + // `includes('PASSWORD_REQUIRED')`) to know it should open the password + // prompt. The "Password" prefix does not cover it — starts_with is + // case-sensitive — and masking it made Trusted invites and trust + // promotion impossible on EVERY node (2026-08-09): the prompt simply + // never opened. + let msg = "PASSWORD_REQUIRED: node password required to grant Trusted"; + assert_eq!(sanitize_error_message(msg), msg); + } + + #[test] + fn tor_unavailable_precondition_passes_through() { + let msg = "Tor address not available. Tor may not be running."; + assert_eq!(sanitize_error_message(msg), msg); + } + #[test] fn seed_reveal_errors_pass_through() { // Every user-actionable seed.reveal failure must reach the user —