From eb498a3c13b1605163d661a160b615772c3f98c0 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sun, 11 Oct 2026 22:29:36 +0200 Subject: [PATCH] chore(security): add OSS Scanner build image and threat model Anthropic's OSS Scanner builds .oss-scanner/Dockerfile from this repository and audits the image offline. The image keeps the source, the Go module and npm caches, the debug panel binary, Xray-core and the sidecars, so tests, the xray gRPC e2e suite, the master+node e2e and a live panel all run with the network disabled. threat_model.md lists the trust boundaries, entry points and the severity rubric the scanner should report against. --- .oss-scanner/Dockerfile | 59 ++++++++++++++++++ .oss-scanner/threat_model.md | 117 +++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 .oss-scanner/Dockerfile create mode 100644 .oss-scanner/threat_model.md diff --git a/.oss-scanner/Dockerfile b/.oss-scanner/Dockerfile new file mode 100644 index 000000000..26cf3d316 --- /dev/null +++ b/.oss-scanner/Dockerfile @@ -0,0 +1,59 @@ +# Build environment for Anthropic's OSS Scanner (https://github.com/anthropics/oss-scanner). +# The scanner builds this with the repository as the context, then audits the image offline: +# everything the build, the tests and a reproducer need must be fetched here. + +FROM node:26-trixie-slim AS node + +FROM golang:1.27.2-trixie + +ENV DEBIAN_FRONTEND=noninteractive \ + CGO_ENABLED=1 \ + CGO_CFLAGS="-D_LARGEFILE64_SOURCE" \ + GOTOOLCHAIN=local + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential ca-certificates curl git jq procps sqlite3 unzip \ + && rm -rf /var/lib/apt/lists/* + +# Node 26 is required: `make gen` imports .ts files directly and needs its type stripping. +COPY --from=node /usr/local/bin/node /usr/local/bin/node +COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules +RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \ + && ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx \ + && node --version && npm --version + +# scanner contract: the checkout lives inside the image, at /src +COPY . /src +WORKDIR /src + +RUN go mod download && go mod verify + +# The Go binary embeds the Vite build from internal/web/dist, so the frontend goes first. +# HUSKY=0 skips the git-hook install, which needs a .git directory the build context may lack. +RUN cd frontend && HUSKY=0 npm ci && npm run build + +# Debug info and no inlining, so stack traces and debuggers map straight to the source. +RUN go build -gcflags=all="-N -l" -o /usr/local/bin/x-ui . \ + && ln -s /usr/local/bin/x-ui /src/x-ui + +# Xray-core, the mtg-multi MTProto sidecar and the geo databases, exactly as the release image ships them. +RUN ./DockerInit.sh "$(dpkg --print-architecture)" \ + && ln -s "/src/build/bin/xray-linux-$(dpkg --print-architecture)" /usr/local/bin/xray \ + && xray version + +# Turns on the xray gRPC end-to-end tests (internal/xray, internal/amneziawgnet) against the real core. +ENV XRAY_E2E_BINARY=/usr/local/bin/xray + +# Baseline tests: also warms the build cache. A failure is logged, never fatal. +RUN go test -count=1 -timeout 30m ./... > /src/.oss-scanner-go-test.log 2>&1 \ + || echo "WARNING: go test reported failures, see /src/.oss-scanner-go-test.log" +RUN cd frontend && npx vitest run --project unit --project components > /src/.oss-scanner-vitest.log 2>&1 \ + || echo "WARNING: vitest reported failures, see /src/.oss-scanner-vitest.log" + +# Reproducers and patches must build with the network gone. +ENV GOPROXY=off \ + GOSUMDB=off \ + GOFLAGS=-mod=readonly \ + npm_config_offline=true + +CMD ["bash"] diff --git a/.oss-scanner/threat_model.md b/.oss-scanner/threat_model.md new file mode 100644 index 000000000..b96992fcd --- /dev/null +++ b/.oss-scanner/threat_model.md @@ -0,0 +1,117 @@ +# 3x-ui threat model + +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. + +## Trust boundaries and attackers + +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: + +1. **Unauthenticated network attacker** reaching the panel port (default 2053) + or the subscription port. Login (`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. +2. **Subscription end users.** They know only their own `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. +3. **Limited API tokens.** `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. +4. **Master/node peers.** A master panel drives child nodes over HTTPS + (`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. +5. **Proxy traffic.** Packets reaching AmneziaWG (`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. +6. **Chat-bot users.** Telegram and Discord bot messages and callback data + (`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. +7. **Imported content** an admin may receive from a third party: share links + and subscription URLs parsed by the import paths (`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. + +## How to exercise it + +- Checkout at `/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`. +- Go tests: `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. +- Master + node end to end: + `go build -o /tmp/x-ui . && XUI_NODE_E2E_BINARY=/tmp/x-ui go test -count=1 -v ./internal/nodee2e/`. +- Fuzz targets: `FuzzParseLink`, `FuzzDecodeCertPin` (`go test -fuzz=...`). +- Frontend: `cd /src/frontend && npx vitest run --project unit --project components`. + The Storybook browser project needs Chromium and is not provisioned. +- Running a live panel: `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). +- Baseline logs from the image build: `/src/.oss-scanner-go-test.log` and + `/src/.oss-scanner-vitest.log`. A built image does not prove the tests passed. +- PostgreSQL is not installed: tests gated on `XUI_TEST_PG_DSN` skip, so a + green run says nothing about the Postgres dialect. + +## Severity + +- **Critical:** unauthenticated remote code execution; authentication bypass + to admin; a limited token, subscription user, bot user or node obtaining + admin control; reading the database or admin credentials unauthenticated. +- **High:** cross-client disclosure of subscription configs or credentials + without knowing the target's subId; scope escape of a `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. +- **Medium:** remotely triggerable crash or resource exhaustion of the panel + process without authentication (it takes down every proxy it supervises); + CSRF on a state-changing admin action; login rate-limit bypass; secret + leakage into logs. +- **Low / informational:** issues that need admin access, local shell access, + or an attacker already able to modify the database or binaries. + +## Reports and patches + +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. + +## Out of scope + +- Anything requiring the admin account or an admin-scope token, including + admin-supplied Xray config, custom geo files, panel update and DB import. +- Vulnerabilities inside Xray-core, mtg-multi, amneziawg-go or other + dependencies, unless 3x-ui itself makes them reachable in a way upstream + does not; report the 3x-ui path. +- `skip` TLS mode accepting any node certificate: it is an explicit, + documented opt-out. +- The `docs/` site, `tools/`, install scripts and CI workflows. +- Missing hardening headers or cookie flags with no demonstrated exploit.