From 0466e8b0bd53dd675746bc6eb49dddc43e8dc3bc Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 11 Oct 2026 18:26:45 +0200 Subject: [PATCH] fix(finalmask): refuse a salamander password xray-core cannot open xray-core needs a salamander (and gecko) password of at least 4 bytes (smPSKMinLen), but checks it only when a socket opens: xray run -test reports Configuration OK. On an inbound the listener then fails and Xray does not start at all, taking every other inbound down; on a client the dial fails. The panel saved any password, and the finalmask editor starts a new salamander mask with an empty one. maskcompat.CheckSalamanderPasswords applies the core's byte rule, pinned against the core's own ListenPacket, and every path that stores a finalmask refuses a short password: inbound add and update, hosts, the subscription's global finalmask, and the inbounds and outbounds of the Xray template. The editor flags it inline as it is typed. --- .../xray/forms/transport/FinalMaskForm.tsx | 20 ++++- frontend/src/test/finalmask-form.test.tsx | 22 +++++ internal/util/maskcompat/salamander.go | 27 ++++++ internal/util/maskcompat/salamander_test.go | 53 ++++++++++++ internal/web/service/host.go | 6 ++ internal/web/service/inbound.go | 25 ++++++ .../inbound_finalmask_salamander_test.go | 83 +++++++++++++++++++ internal/web/service/setting.go | 3 + internal/web/service/xray_setting.go | 13 +++ 9 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 internal/util/maskcompat/salamander.go create mode 100644 internal/util/maskcompat/salamander_test.go create mode 100644 internal/web/service/inbound_finalmask_salamander_test.go diff --git a/frontend/src/lib/xray/forms/transport/FinalMaskForm.tsx b/frontend/src/lib/xray/forms/transport/FinalMaskForm.tsx index 5c45dc81b..3652dfa7d 100644 --- a/frontend/src/lib/xray/forms/transport/FinalMaskForm.tsx +++ b/frontend/src/lib/xray/forms/transport/FinalMaskForm.tsx @@ -88,6 +88,20 @@ function validateGeckoPacketSize(_rule: unknown, value: unknown): Promise ); } +// xray-core's salamander smPSKMinLen counts bytes; a shorter password loads, then the listener +// (stopping Xray) or the dial fails, so the backend refuses it at save too. +const SALAMANDER_MIN_PSK_BYTES = 4; + +function validateSalamanderPassword(_rule: unknown, value: unknown): Promise { + const bytes = new TextEncoder().encode(typeof value === 'string' ? value : '').length; + if (bytes >= SALAMANDER_MIN_PSK_BYTES) return Promise.resolve(); + return Promise.reject( + new Error( + `Use at least ${SALAMANDER_MIN_PSK_BYTES} bytes; xray-core refuses a shorter password`, + ), + ); +} + function asPath(name: NamePath): (string | number)[] { return Array.isArray(name) ? [...name] : [name]; } @@ -1198,7 +1212,11 @@ function SalamanderUdpMaskSettings({ - +