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

v0.7.0 (released 2026-09-20) · pure Rust (no Go Xray-core inside the binary) · wasm32-wasip2 · wasmtime · image ghcr.io/harodggg/xray-wasm:v0.7.0

It runs inside a sandboxed WASI runtime — which is neither a macOS app nor XrayTun's TUN mode. Start with how it relates to XrayTun.

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.

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

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:

Those numbers come from the project's own repository documents; this page does not recompute them. Follow the links above to check.

Documents

Known boundaries (what to know before you use it)

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.