Two subtle race conditions in the browser WebSocket client:
1. Stale-event clobber. When connect() is called while the old socket is
in CLOSING state, the readyState guard falls through and a new socket
is assigned to this.ws. The old socket's queued close event then
nulls out this.ws, silently breaking send() until the next reconnect.
Same risk for delayed open/error/message handlers.
2. Reconnect-after-disconnect. clearTimeout() does not cancel a callback
that has already fired but whose macrotask has not yet run. If
disconnect() lands in that window, the queued reconnect callback
still calls #openSocket() and resurrects the connection.
Every event handler now bails out if this.ws no longer points at the
socket that fired the event, and the reconnect timer callback re-checks
shouldReconnect before opening a new socket.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Implement CSRF protection and security hardening across the application
- Added CSRF token handling in axios requests and HTML templates.
- Introduced CSRF middleware to validate tokens for unsafe HTTP methods.
- Implemented login limiter to prevent brute-force attacks.
- Enhanced security headers in middleware for improved response security.
- Updated login notification to include safe metadata without passwords.
- Added tests for CSRF middleware and login limiter functionality.
* fix
Audit panel xhttp config against xray-core's runtime paths and split
fields per direction so each side carries only what it actually uses:
- Bidirectional (must match): host, path, mode, all xPadding*,
session*/seq*, uplinkData*/Key, scMaxEachPostBytes
- Server-only (inbound): noSSEHeader, scMaxBufferedPosts,
scStreamUpServerSecs, serverMaxHeaderBytes
- Client-only (outbound): uplinkHTTPMethod, uplinkChunkSize,
noGRPCHeader, scMinPostsIntervalMs, xmux
The inbound previously held client-only fields and the outbound was
missing every must-match field beyond host/path/mode — meaning a
panel-built outbound couldn't connect to an inbound with a custom
xPaddingKey/sessionKey/etc.
Headers stay on the inbound for URL-share purposes only; xray's
listener ignores them at runtime, but they travel through the share
link's `extra` blob so the client picks them up.
Renames the URL helpers (applyXhttpPadding* -> applyXhttpExtra*) since
the blob now carries more than padding, and folds path/host/mode into
the helper so each link generator's xhttp branch is one line.
Adds two enforcement points for xray's "uplinkHTTPMethod=GET only in
packet-up" rule: the GET option is disabled when mode != packet-up,
and a watcher on the outbound modal auto-clears GET when the user
switches modes.
Hides the XMUX block behind an `enableXmux` switch on the outbound
form (mirrors the QUIC Params toggle) so the section doesn't clutter
the form by default; fromJson auto-flips it on for outbounds with
saved xmux config.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
selectedAuth was UI-only metadata (Xray never reads it) and entirely
redundant with the encryption string itself — the dropdown only
controlled which block from `xray vlessenc` to apply. Replace it with
two explicit buttons ("X25519" and "ML-KEM-768") so the user picks
the auth mode in one click instead of dropdown + Get-New-Keys.
- VLESSSettings drops the field from constructor, fromJson, and toJson;
legacy `selectedAuth` values still in DB will be silently shed on the
next save.
- getNewVlessEnc(authLabel) now takes the label as a parameter; clear
resets only decryption/encryption.
- Fallbacks visibility now keys on encryption === "none" (the same
thing the dropdown was effectively gating on).
- Info modal drops the redundant Authentication tag and colours the
encryption tag red when it's "none", green otherwise.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
testseed is only meaningful for the exact xtls-rprx-vision flow, but the
panel was emitting it for any non-empty flow (including the UDP variant)
and keeping it on the inbound after the flow was cleared via the client
modal. Tighten the gate end-to-end:
- VLESSSettings.toJson (inbound + outbound) now only emits testseed when
the flow is exactly xtls-rprx-vision and the array is 4 positive ints;
default state is empty so unmodified inbounds omit the field entirely.
- canEnableVisionSeed drops the udp443 variant per spec.
- Form adds a tooltip + theme-aware help text and an inline error when
the user partially fills the four inputs; submit is blocked in that
state. Reset clears to empty (= use server defaults).
- UpdateInboundClient strips a now-orphaned testseed when the spliced
client no longer leaves any XRV flow in the inbound.
- MigrationRequirements cleans up legacy rows where testseed lingered
after flow changes or was saved for non-XRV flows by older versions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Track and surface a subscription's enabled state from backend to frontend so the UI can show inactive subscriptions and use it in active-state logic.
Changes:
- sub/subService.go: track hasEnabledClient, set traffic.Enable, add Enabled to PageData and populate it in BuildPageData.
- sub/subController.go: include enabled in the page context.
- web/html/settings/panel/subscription/subpage.html: emit data-enabled attribute and render an "inactive" tag when disabled.
- web/assets/js/subscription.js: read data-enabled and include it in isActive() checks.
This ensures subscriptions with no enabled clients are marked inactive in the UI and excluded from being considered active.
Add a configurable option to restart Xray when clients are auto-disabled and persist disable actions.
Changes include:
- New setting restartXrayOnClientDisable (default true), getters/setters in SettingService, UI toggle in general settings, and translations for multiple locales.
- AddTraffic signature updated to return a third bool (clientsDisabled). disableInvalidClients now calls Xray API to remove users, marks client_traffics.enable=false, updates inbound.Settings JSON so clients appear disabled in stored settings, and returns appropriate counts/errors.
- XrayTrafficJob now checks the clientsDisabled flag and restarts Xray when the setting is enabled (with fallback to mark Xray as needing restart on failure).
- XrayService.GetXrayConfig call adjusted to ignore AddTraffic returns.
- Subscription generation (subService/subJson/subClash) no longer filters clients by their enable flag when matching subId.
- Minor fixes: check_client_ip_job now checks scanner.Err and improved API error handling/logging.
These changes ensure auto-disabled clients are propagated to Xray and the stored inbound settings, and provide an option to restart Xray automatically after auto-disable events.
#4142
Adjust QUIC parameter defaults and tighten form validation across inbound/outbound components.
- Set default brutalUp/brutalDown to 65537 and only include them in JSON when congestion is 'brutal' or 'force-brutal'.
- Change keepAlivePeriod defaults (inbound QUIC -> 5s, Hysteria stream -> 2s) and enforce minimums in the UI.
- Expose and serialize additional QUIC fields in outbound QuicParams: init/max stream windows, init/max connection windows, maxIdleTimeout, disablePathMTUDiscovery, maxIncomingStreams.
- Add UI min/placeholder constraints: stream/connection receive windows min=16384 and updated placeholders to show defaults, brutal fields min=65537, maxIncomingStreams min=8 (placeholders updated), keepAlive min adjusted.
- Add Wireguard and Hysteria entries to Protocols.
Touched files: web/assets/js/model/inbound.js, web/assets/js/model/outbound.js, web/html/form/outbound.html, web/html/form/stream/stream_finalmask.html.
Add KCP-specific fields mtu and tti to inbound stream handling in web/assets/js/model/inbound.js. The changes add obj.mtu/obj.tti when serializing the kcp stream and set params for mtu and tti in the various KCP parameter-building branches so these values are preserved and transmitted where KCP is used.
* Fix: propagate xhttp xPadding settings into generated subscription links
The four `genXLink` helpers in `sub/subService.go` only copied `path`,
`host` and `mode` out of `xhttpSettings` when building vmess:// /
vless:// / trojan:// / ss:// URLs. Everything else — `xPaddingBytes`,
`xPaddingObfsMode`, `xPaddingKey`, `xPaddingHeader`,
`xPaddingPlacement`, `xPaddingMethod` — was silently dropped.
That meant an admin who set, say, `xPaddingBytes: "80-600"` plus obfs
mode with a custom `xPaddingKey` on the inbound had a server config
that no client could match from the copy-pasted link: the client kept
the xray/sing-box internal defaults (`100-1000`, `x_padding`,
`Referer`), hit the server, and was rejected by
invalid padding (queryInHeader=Referer, key=x_padding) length: 0
The user-visible symptom on OpenWRT / Podkop / sing-box was
"xhttp inbound just won't connect" — no obvious pointer to what was
actually wrong because the link itself *looks* complete.
Fix:
* New helper `applyXhttpPaddingParams(xhttp, params)` writes
`x_padding_bytes=<range>` (flat, sing-box family reads this) and
an `extra=<url-encoded-json>` blob carrying the full set of xhttp
settings (xray-core family reads this). Both encodings are emitted
side-by-side so every mainstream client can pick at least one up.
* All four link generators (`genVmessLink` via the obj map,
`genVlessLink`, `genTrojanLink`, `genShadowsocksLink`) now invoke
the copy.
* Obfs-only fields (`xPaddingKey`, `xPaddingHeader`,
`xPaddingPlacement`, `xPaddingMethod`) are only included when
`xPaddingObfsMode` is actually true and the admin filled them in.
An inbound with no custom padding produces exactly the same URL
as before — existing subscriptions are unaffected.
* Also propagate xhttp xPadding settings into the panel's own Info/QR links
The previous commit covered the subscription service
(sub/subService.go). The admin-panel side — the "Copy URL" / QR /
Info buttons inside inbound details — has four more
xhttp-emitting link generators in `web/assets/js/model/inbound.js`
(`genVmessLink`, `genVLESSLink`, `genTrojanLink`, `genSSLink`) that
had the exact same gap: only `path`, `host` and `mode` were copied.
Mirror the server-side fix on the client:
* Add two static helpers on `Inbound`:
- `Inbound.applyXhttpPaddingToParams(xhttp, params)` for
`vless://` / `trojan://` / `ss://` style URLs — writes
`x_padding_bytes=<range>` (sing-box family) and
`extra=<url-encoded-json>` (xray-core family).
- `Inbound.applyXhttpPaddingToObj(xhttp, obj)` for the VMess base64
JSON body — sets the same fields directly on the object.
* Call them from all four link generators so an admin who enables
obfs mode + a custom `xPaddingKey` / `xPaddingHeader` actually
gets a working URL from the panel.
* Only non-empty fields are emitted, so default inbounds produce
exactly the same URL as before.
Also fixes a latent positional-args bug in
`web/assets/js/model/outbound.js`: both VMess-JSON (L933) and
`fromParamLink` (L975) were calling
`new xHTTPStreamSettings(path, host, mode)` — but the 3rd positional
arg of the constructor is `headers`, not `mode`, so `mode` was
landing in the `headers` slot and the actual `mode` field stayed at
its default. Construct explicitly and set `mode` by name; while
here, also pick up `x_padding_bytes` and the `extra` JSON blob from
the imported URL so the symmetric case of importing a padded link
works too.
---------
Co-authored-by: pwnnex <eternxles@gmail.com>
`genHysteriaLink` was calling `.join(',')` on
`this.stream.tls.settings.echConfigList`, but that field is bound to an
`<a-input>` (single-line string) in `tls_settings.html` and defaults to
`''` in `TlsStreamSettings.Settings`. Calling `.join()` on a string
throws `TypeError: echConfigList.join is not a function`, which breaks
the Info / QR buttons for every hysteria / hysteria2 inbound.
All three sibling link generators (`genVmessLink`, `genVlessLink`,
`genTrojanLink`) already pass the value directly:
params.set("ech", this.stream.tls.settings.echConfigList)
`URLSearchParams.set` will stringify arrays with `,` on its own, so the
same one-liner works for both string and array inputs. Align
`genHysteriaLink` with the other three.
Fixes#4063
Co-authored-by: pwnnex <eternxles@gmail.com>
Treat the xdns mask type as a multi-value setting and update forms accordingly. Inbound/outbound UdpMask models now return arrays for xdns (inbound: settings.domains, outbound: settings.resolvers) using Array.isArray checks. UI templates were split so 'header-dns' still uses a single domain string, while 'xdns' renders a tags-style <a-select> for multiple entries (domains/resolvers). Conditionals were made explicit (mask.type === ...) instead of using includes(). Changed files: web/assets/js/model/inbound.js, web/assets/js/model/outbound.js, web/html/form/outbound.html, web/html/form/stream/stream_finalmask.html.
Change TunSettings to support separate IPv4/IPv6 MTU values and add gateway, DNS, autoSystemRoutingTable and autoOutboundsInterface properties. Introduces _normalizeMtu to accept legacy single-value or array forms and provide sensible defaults. Update fromJson/toJson to handle new fields and preserve backward compatibility. Update tun form UI to expose MTU IPv4/IPv6 inputs, Gateway/DNS tag selects, Auto Routing Table and Auto Outbounds input.
Expose an ipsBlocked array on Outbound.FreedomSettings and wire it into the outbound form. The constructor now defaults fragment to {} and noises/ipsBlocked to arrays for robustness; fromJson/toJson handle ipsBlocked and omit it when empty. The outbound HTML adds a tag-style <a-select> bound to outbound.settings.ipsBlocked (with comma tokenization and placeholder) so users can enter IP/CIDR/geoip entries.
Replace legacy KCP buffer options with cwndMultiplier and maxSendingWindow across models and UI. Updated KcpStreamSettings in web/assets/js/model/inbound.js and web/assets/js/model/outbound.js (constructor, fromJson and toJson) to remove congestion/readBuffer/writeBuffer and use cwndMultiplier/maxSendingWindow instead. Updated web/html/form/outbound.html to reflect the new KCP fields in the stream form and to include extensive template formatting/markup cleanup for consistency and readability.
Configure session cookie options centrally in initRouter and remove per-login MaxAge handling. Deleted SetMaxAge helper and its use in the login flow; session.Options are now applied once using basePath with HttpOnly and SameSite defaults, and MaxAge is set only when the stored setting is available and >0. Also make CookieManager.setCookie treat exdays as optional (only add expires when provided) and stop using a hardcoded 150-day expiry for the lang cookie in the JS language manager.
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
* docs(agents): add AI agent guidance documentation
* feat(sub): add Clash/Mihomo YAML subscription service
Add SubClashService to convert subscription links to Clash/Mihomo
YAML format for direct client compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(sub): integrate Clash YAML endpoint into subscription system
- Add Clash route handler in SUBController
- Update BuildURLs to include Clash URL
- Pass Clash settings through subscription pipeline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(web): add Clash settings to entity and service
- Add SubClashEnable, SubClashPath, SubClashURI fields
- Add getter methods for Clash configuration
- Set default Clash path to /clash/ and enable by default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ui): add Clash settings to subscription panels
- Add Clash enable switch in general subscription settings
- Add Clash path/URI configuration in formats panel
- Display Clash QR code on subscription page
- Rename JSON tab to "Formats" for clarity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(js): add Clash support to frontend models
- Add subClashEnable, subClashPath, subClashURI to AllSetting
- Generate and display Clash QR code on subscription page
- Handle Clash URL in subscription data binding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
* feat: implement real-time traffic monitoring and UI updates using a high-performance WebSocket hub and background job system
* feat: add bulk client management support and improve inbound data handling
* Fix bug
* **Fixes & Changes:**
1. **Fixed XPadding Placement Dropdown**:
- Added the missing `cookie` and `query` options to `xPaddingPlacement` (`stream_xhttp.html`).
- *Why:* Previously, users wanting `cookie` obfuscation were forced to use the `header` placement string. This caused Xray-core to blindly intercept the entire monolithic HTTP Cookie header, failing internal padding-length validations and causing the inbound to silently drop the connection.
2. **Fixed Uplink Data Placement Validation**:
- Replaced the unsupported `query` option with `cookie` in `uplinkDataPlacement`.
- *Why:* Xray-core's `transport_internet.go` explicitly forbids `query` as an uplink placement option. Selecting it from the UI previously sent a payload that would cause Xray-core to instantly throw an `unsupported uplink data placement: query` panic. Adding `cookie` perfectly aligns the UI with Xray-core restrictions.
### Related Issues
- Resolves#3992
* This commit fixes structural payload issues preventing XHTTP from functioning correctly and eliminates WebSocket log spam.
- **[Fix X-Padding UI]** Added missing `cookie` and `query` options to X-Padding Placement. Fixes the issue where using Cookie fallback triggers whole HTTP Cookie header interception and silent drop in Xray-core. (Resolves [#3992](https://github.com/MHSanaei/3x-ui/issues/3992))
- **[Fix Uplink Data Options]** Replaced the invalid `query` option with `cookie` in Uplink Data Placement dropdown to prevent Xray-core backend panic `unsupported uplink data placement: query`.
- **[Fix WebSockets Spam]** Boosted `maxMessageSize` boundary to 100MB and gracefully handled fallback fetch signals via `broadcastInvalidate` to avoid buffer dropping spam. (Resolves [#3984](https://github.com/MHSanaei/3x-ui/issues/3984))
* Fix
* gofmt
* fix(websocket): resolve channel race condition and graceful shutdown deadlock
* Fix: inbounds switch
* Change max quantity from 10000 to 500
* fix