fix(rpc): let the PASSWORD_REQUIRED sentinel through the error sanitizer
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
7ec4bdb22d
commit
e5a8fce198
@@ -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 —
|
||||
|
||||
Reference in New Issue
Block a user