3x-ui is a self-hosted web control panel for Xray-core. One Go binary stores
inbounds, clients and settings in SQLite (default) or PostgreSQL, renders an
Xray JSON config from that state, supervises Xray-core and the mtg-multi
MTProto sidecar as child processes, runs AmneziaWG and TUIC servers in-process,
and serves an embedded React SPA. It is deployed on internet-facing VPS hosts,
usually as root, often in censored regions where a compromise exposes the
people relying on the proxy. Read docs/architecture.md in the scanned revision
for the code map; do not assume package names stay fixed.
Trusted: the panel administrator (one account, or an admin-scope API token). The admin can already edit the full Xray config, run a panel update, import a database and set file paths, so admin-to-root is by design and out of scope.
Untrusted, in decreasing order of importance:
POST /login, 2FA, LDAP), CSRF/session
handling (internal/web/session/), the login rate limiter
(controller/login_limiter.go), the checkAPIAuth gate in
controller/api.go (session, Bearer token, mTLS client cert),
/csrf-token, PWA/static routes, and the WebSocket /ws upgrade.subId and fetch
{subPath}/{subId}, the JSON/Clash/Mihomo variants and hwid-status
(internal/sub/). Each must see only their own clients; check subId
guessing, cross-client leakage, header injection, forwarded-header trust
(internal/sub/forwarded_trust.go) and the public subscription page.monitor and node-sync scopes are restricted by
explicit allowlists in controller/api.go (enforceTokenScope). Any way
for such a token to reach a route outside its allowlist is a vulnerability.internal/web/runtime/, TLS modes verify/skip/pin/mtls). A compromised
or malicious node must not gain control of the master or of other nodes;
node responses (inbound lists, traffic, online clients, certificates,
descendants) are untrusted input on the master.internal/amneziawgnet/),
TUIC (internal/tuic/) and the loopback SOCKS5 relays into Xray. A peer
must not escape its per-peer auth, reach another peer's traffic, or crash
the panel process.internal/web/service/tgbot/, discord/). Non-admin chats may only see
their own client (bound via the subId invite token); admin commands must
be unreachable from them.FuzzParseLink), and
geosite/geoip .dat files (internal/xray/geodata/).Also in scope: stored or reflected XSS in the SPA or the subscription page from client remarks, emails, inbound tags or any other value a non-admin can influence; SQL injection through GORM raw queries; path traversal in file or certificate handling; secrets leaking into logs, subscription output, API responses for limited tokens, or node sync payloads.
/src; the panel binary is /usr/local/bin/x-ui (debug build),
Xray-core is /usr/local/bin/xray, and the sidecars and geo files are in
/src/build/bin.cd /src && go test -count=1 ./.... Real-DB helpers live in
internal/database/dbtest (dbtest.InitDB(t, path)); internal/sub's
initSubDB(t) is the template for subscription tests. httptest is used
for HTTP. XRAY_E2E_BINARY is set, so the xray gRPC end-to-end tests run.go build -o /tmp/x-ui . && XUI_NODE_E2E_BINARY=/tmp/x-ui go test -count=1 -v ./internal/nodee2e/.FuzzParseLink, FuzzDecodeCertPin (go test -fuzz=...).cd /src/frontend && npx vitest run --project unit --project components.
The Storybook browser project needs Chromium and is not provisioned.XUI_DB_FOLDER=/tmp/xui XUI_LOG_FOLDER=/tmp/xui
XUI_BIN_FOLDER=/src/build/bin x-ui (creates admin/admin on first start;
x-ui setting -h changes port, base path and credentials)./src/.oss-scanner-go-test.log and
/src/.oss-scanner-vitest.log. A built image does not prove the tests passed.XUI_TEST_PG_DSN skip, so a
green run says nothing about the Postgres dialect.monitor/node-sync
token; stored XSS that a non-admin can plant in the admin panel; a
malicious node compromising its master; SQL injection reachable by a
non-admin; peer isolation bypass in AmneziaWG/TUIC.Name the scanned commit, the attacker's starting position from the list
above, the configuration needed (default or not), the entry route, and the
full path to impact. Give a deterministic reproducer, ideally a Go test in the
package that owns the bug, using the real DB helpers rather than mocks.
Patches should fix the root cause, follow CLAUDE.md in the repository
(comments at most two lines, stdlib testing, state-changing inbound/client
operations dispatched through runtime.Runtime), and include a regression
test that fails without the fix. Merge findings sharing one root cause.
skip TLS mode accepting any node certificate: it is an explicit,
documented opt-out.docs/ site, tools/, install scripts and CI workflows.