The server-side fetch used sponsors.sanaei.dev, a hostname unique to
3x-ui. Without ECH it travels in plaintext SNI, so an on-path observer
could tag a server as running the panel from that one connection.
raw.githubusercontent.com is shared by countless tools and reveals
nothing. The path stays inside TLS. The data source is unchanged: the
same MHSanaei/sponsors repo.
Refs #6824
The sidebar no longer carries a sponsor card. The backend stops serving
the "sidebar" slot, so a sponsors.json entry that still lists it keeps
only its other slots and one listing nothing else is dropped. Its
rotation, three-sponsor cap and collapsed-rail logo rendering go with it.
The Sponsors page now lists only real sponsors: the "Your brand here"
card shows only as the empty state instead of sitting in the grid and
repeating the header's Become a sponsor button, and the Available
placements section and its slot-occupancy helper are removed. Cards are
larger, two per row on wide screens. Unused i18n keys are dropped from
every locale.
sponsors.json only had an end date, so a booked placement had to be
added to the file on the day it started. An optional `from` now hides
a sponsor (and its logo) until that instant; entries without it show
immediately as before.
The public /sponsors/logo/:name route only accepted names matching an
active sponsor's logo, which was already regex-filtered, but that guard
was indirect. Checking sponsorLogoRe on the name itself makes the
path/URL safety local and clears CodeQL alerts #113 (go/request-forgery)
and #114 (go/path-injection).
Monthly sponsor placements need to change without cutting a panel
release. Panels now read 3X/sponsors.json from the MHSanaei/sponsors
repo (GitHub Pages on sponsors.sanaei.dev) and show active sponsors in
four slots: an overview banner, a rotating sidebar card (max three), the
login page and a new Sponsors page that also lists open placements.
An entry shows only while enable is not false and until is in the
future; links must be https and logos are png/webp/jpg by name only.
The list is cached for an hour and the last good copy survives upstream
failures; logos are proxied through /sponsors/logo/:name with failures
cached, so CSP stays 'self' and admin browsers never reach a third
party. Admins can hide a slot for 24h. Under XUI_DEBUG the panel reads
a sibling ../sponsors/3X checkout so edits can be previewed before push.