xray-wasm: Xray's VLESS + XTLS-Vision + REALITY protocol stack rewritten in pure Rust, compiled to WebAssembly, running a real TCP proxy
xray-wasm is an independent implementation of the protocol stack: it rewrites
Xray's VLESS + XTLS-Vision + REALITY in pure Rust, compiles it to a
wasm32-wasip2 component, and runs it under wasmtime (or any
compatible WASI Preview 2 runtime / container) as a real TCP proxy — not a demo,
not a simulator. It works in two directions: client (local socket → REALITY
server) and server (REALITY inbound → destination, written for container
environments such as k3s).
View the repo harodggg/xray-wasm Container image v0.7.0 · amd64 + arm64
How it relates to XrayTun: two independent projects by the same author
This section comes first because it is the thing most often mixed up — by people and by AI alike.
- XrayTun is a macOS GUI client that ships the official Go Xray-core inside its bundle. XrayTun does not use xray-wasm: there is no WebAssembly in its implementation, and its protocol stack was not replaced with Rust.
- xray-wasm is a different technical route: an independent, pure-Rust implementation of the protocol stack, aimed at containers, servers and automation, running inside a WASI runtime, with no graphical interface.
- The two projects are simply sibling projects under the same author and the same GitHub account (harodggg): separate repositories, separate version numbers, separate licenses, separate release processes.
- Do not mix up the version numbers. This page describes xray-wasm v0.7.0; XrayTun's current version is a different sequence (v0.8.26, see the home page).
What problem it solves
A protocol stack is usually tied to a native binary.
Implementations in the Xray ecosystem are almost all written in Go (official Xray-core /
V2Ray). If you want to put a "VLESS + Vision + REALITY client" into an environment that is
sandboxed and awkward for native binaries — a WASI runtime, a strongly
isolated container, an edge node — then a Go binary is the wrong shape. xray-wasm compiles the
stack into a wasm32-wasip2 component: the host provides capabilities such
as TCP, and the protocol stack runs inside the guest, with the boundary drawn by the
runtime rather than by process privileges.
The server side works the other way round.
Besides the client direction there is a server subcommand: REALITY inbound →
forward to a destination site. It was written for orchestrated environments such as k3s —
configuration comes from environment variables and keys come from Secrets.
Designed to be assertable.
The repository README has a section aimed at automation agents ("quick reference for
automation agents") where every step is expected to produce an assertable output. Both
directions also support a configuration self-check only mode (environment
variable XT_CHECK or the --check flag): it prints the effective
fingerprint / no-flow / client-ver / server / listen values (UUID masked) and exits
without listening on any port.
Its security posture is stated explicitly, not implied.
The client binds to loopback only by default, and prints a loud "open proxy"
warning if it is bound to a non-loopback address without authentication. The server escapes
peer-controlled fields (sni / target) before writing them to the
log, so a newline smuggled into an SNI cannot forge a log line. A misspelled fingerprint name
fails at startup instead of silently falling back to a default.
The two directions, and how to run them
The two commands below are the exact usage from the repository README. The image
ghcr.io/harodggg/xray-wasm:v0.7.0 provides linux/amd64 and
linux/arm64, and can be pulled anonymously (no
docker login needed).
Direction 1: client (socket → REALITY, the default)
With no subcommand you get the client: a local SOCKS5 entry point whose outbound goes through
REALITY. In the example below -p 127.0.0.1:1080:1080 exposes the port on
loopback only (consistent with its loopback-by-default posture), while the
listen address inside the container is set to 0.0.0.0:1080 by
XT_LISTEN.
docker run --rm -p 127.0.0.1:1080:1080 \
-e XT_LISTEN=0.0.0.0:1080 \
-e XT_SERVER=<ip:port> -e XT_PBK=<public key> -e XT_SID=<shortId> \
-e XT_SNI=<spoofed domain> -e XT_UUID=<uuid> \
ghcr.io/harodggg/xray-wasm:v0.7.0
XT_SERVER: the REALITY server'sip:port.XT_PBK: the server's X25519 public key;XT_SID: the shortId.XT_SNI: the spoofed domain (SNI);XT_UUID: the VLESS user UUID.
Direction 2: server (REALITY → destination, the server subcommand)
This is the direction written for orchestrated environments such as k3s: it takes REALITY
inbound traffic on local port 8443 and forwards traffic that fails
authentication verbatim to the real site named by XT_DEST. The four
variables XT_PRIVATE_KEY / XT_SHORT_IDS /
XT_SERVER_NAMES / XT_DEST / XT_USERS are all required;
if any is missing it refuses to start and says why.
docker run --rm -p 8443:8443 \
-e XT_PRIVATE_KEY=<private key> -e XT_SHORT_IDS=<shortId> \
-e XT_SERVER_NAMES=<spoofed domain> -e XT_DEST=<host:port of that same domain> \
-e XT_USERS=<uuid> \
ghcr.io/harodggg/xray-wasm:v0.7.0 server
XT_SERVER_NAMES must point at the same site as XT_DEST, otherwise
the REALITY disguise does not hold. Note that the server's listen address variable is
XT_SERVER_LISTEN, which is not the same variable as the
client's XT_LISTEN.
Configuration self-check: validate the flags before listening
Both directions support a self-check that prints the effective configuration and exits
without listening on any port: add -e XT_CHECK=1 (or
--check on the command line). That mode is a good fit for an
initContainer in an orchestrator.
Verification status
A rewritten protocol stack that never talks to a real peer proves nothing. xray-wasm's verification is:
- 100 unit tests (as recorded in the repository README and
docs/verification-log.md). -
Real end-to-end runs in both directions, with official Xray-core as the peer,
run continuously in CI:
.github/workflows/ci.ymlhas two jobs —e2econnects this project's wasm client to a real Xray REALITY server, ande2e-serverconnects the official Xray client to this project's wasm REALITY server. - Raw output for each verification is recorded in docs/verification-log.md, including what did not pass and why an earlier conclusion was overturned.
Those numbers come from the project's own repository documents; this page does not recompute them. Follow the links above to check.
Documents
- verification-log.md: the verification log (what each run did, raw output, conclusions)
-
docs/: design documents
and plans —
fingerprint-plan.md,fingerprint-security.md,vision-server-plan.md,port-map.md,issue-board.md
Known boundaries (what to know before you use it)
- It is a WASI component, not a native program and not an application. Running it requires wasmtime or a compatible WASI Preview 2 runtime (the container image packages the runtime and the component together). There is no GUI, and there is no "double-click to install".
- The exit code is only observable as "0 / non-zero" (see the amber block above). Orchestration logic that depends on a specific exit code has to change.
-
Destination hostname resolution inside the guest can take up to 30 seconds to
fail. Resolution uses a host capability (wasmtime's
allow-ip-name-lookup), and failure is not immediate. The repository README lists this under "read before deploying". - The client binds to loopback only by default; bind it to a non-loopback address without authentication and you have an open proxy. The project prints a warning, but it will not stop you — that deployment decision is yours.
- The facts on this page describe v0.7.0 (2026-09-20). For later versions, go to the repository; this page does not speak for unreleased versions.
Frequently asked questions
How does xray-wasm relate to XrayTun?
They are two independent projects by the same author. XrayTun is a macOS GUI client that ships the official Go Xray-core in its bundle, and it does not use xray-wasm; xray-wasm is an independent pure-Rust implementation of the protocol stack that runs inside a WASI runtime, aimed at containers and servers. The repositories, version numbers, licenses and release processes are all separate.
Does xray-wasm need Go or the official Xray binary to run?
No. The protocol stack is implemented in pure Rust and the artifact is a
wasm32-wasip2 component that runs under wasmtime. Official Xray-core appears only
during verification: CI uses it as the real peer to check interoperability in
both directions.
What is the license?
The repository contains two license files: LICENSE and
LICENSE.meow-rs (the latter is Copyright (c) 2026 Max Lv), and
Cargo.toml declares license = "MIT". Because of the extra
third-party license file, GitHub identifies the repository as Other
rather than MIT (the API returns NOASSERTION). So when citing it, write
"MIT (plus a third-party LICENSE.meow-rs, see the repository)"
rather than plain "MIT".
Can the container image be pulled directly, and which platforms does it support?
Yes — it can be pulled anonymously, with no login to a container registry:
ghcr.io/harodggg/xray-wasm:v0.7.0 provides linux/amd64 and
linux/arm64.
Does it replace XrayTun's TUN mode?
No, they solve different problems. XrayTun's TUN mode uses the official Xray-core in its bundle to create a utun interface and change routes and DNS, taking over the whole Mac's traffic; xray-wasm is a different route that puts the protocol stack into a sandboxed runtime, with no TUN, no system network configuration and no UI.
Links
- Source repository: github.com/harodggg/xray-wasm
- All releases: GitHub Releases
- Container image: ghcr.io/harodggg/xray-wasm
- Verification log: docs/verification-log.md
- Design documents: docs/
- XrayTun (the macOS client this site is about): home page
- Site summary for AI: llms.txt