From e5a8fce1989d529c222878fa60860d1476169a99 Mon Sep 17 00:00:00 2001 From: archipelago Date: Sun, 9 Aug 2026 07:49:56 -0400 Subject: [PATCH] fix(rpc): let the PASSWORD_REQUIRED sentinel through the error sanitizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No node in the fleet could mint a Trusted federation invite or promote a peer to Trusted from the dashboard. The cause is one case comparison. Granting Trusted requires the node password; when none is supplied the handler bails with "PASSWORD_REQUIRED: node password required to grant Trusted" — a sentinel the frontend machine-reads (isPasswordRequired checks includes('PASSWORD_REQUIRED')) to know it should open the password prompt. sanitize_error_message allowlists "Password", but starts_with is case-sensitive, so the ALL-CAPS sentinel failed the allowlist and collapsed into "Operation failed. Check server logs for details." The frontend never saw the sentinel, the prompt never opened, and the flow died with a generic error on every node. Reproduced via direct RPC: federation.invite {trust_level:"trusted"} without a password returned the generic message; the same call WITH the password minted a code fine — the machinery was healthy, only the signal was being eaten. "Tor address not available. Tor may not be running." — the invite handler's other user-actionable precondition — was masked the same way and is allowlisted too. Regression tests pin both passing through verbatim, and internal_errors_stay_generic still passes, so the generic masking of real internals is unchanged. Co-Authored-By: Claude Fable 5 --- core/archipelago/src/api/rpc/middleware.rs | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) 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 —