Changelog
Everything merged into dev since the project started (146 pull requests), by area. Within an area the order is by PR number. Links go to the PRs in getmikan/Citrus, which describe what was wrong, what changed and how it was tested.
The first beta release, v1.0.0-beta.1, is built from dev up to #112. The second, v1.0.0-beta.2, is built from dev as described on this page.
What changes behaviour for existing configs is collected on Migrating from mihomo.
Infrastructure, CI and docs
Section titled “Infrastructure, CI and docs”- #2
devsynced withmain. - #3 Security checks in CI (gofmt, vet,
go mod tidy, govulncheck, CodeQL, dependency review) and CONTRIBUTING.md. - #4 The Citrus site on GitHub Pages, with news.
- #12 Slim CI for PRs (about 8 jobs); the full build only on
mainand tags. - #13 News post on post-quantum REALITY, XHTTP and removed protocols.
- #24 Weekly sync from mihomo Alpha (the workflow; not yet enabled in the settings).
- #25 Interop suite against Xray-core (REALITY PQ, XHTTP, ECH) and its CI job.
- #51 Load and benchmark harness and a first baseline.
- #74 Interop suite: mihomo v1.19.31 and v1.19.32 clients against a Citrus server.
- #98 Site pages: roadmap, changelog, compatibility, migrating from mihomo and security; landing refresh.
- #99 CI: site- and docs-only PRs skip the Go checks; the site is built on site PRs.
- #101 Server comparison of Xray-core, mihomo and Citrus: throughput, CPU per GB, new connections.
- #108 Changelog brought up to the v1.0.0-beta.1 release.
- #110 Go 1.26:
x/net,x/crypto,x/textandx/sysupdated (HTTP/2 and other vulnerabilities fixed), separate-go120…-go125builds dropped; Windows 7/8 and older macOS are covered by the patched MetaCubeX toolchain, Linux needs kernel 3.2+. - #112 Changelog: #108, #110 and #111.
- #114 CI: pinned Go toolchain for tests, the PR build and the bench.
- #120 LF for
*.shand Dockerfile; triage of-racereports inlistener/inbound. - #121 CI: a
-racejob for the 30 race-clean packages. - #126 Four-server comparison: Xray, sing-box, mihomo and Citrus, eight protocols.
- #127 Bench: deterministic REALITY upload with TLS inside the tunnel; a nightly performance check against mihomo in CI.
- #144 Optional non-root server profiles: dedicated systemd account with read-only config and bounded capabilities, and a Docker image using UID 65532. CI runs the real systemd unit and a zero-capability Docker relay test.
- #132 Changelog brought up to the second beta, v1.0.0-beta.2.
- #134 News post on the second beta, the roadmap after it.
- #135 Test and site dependencies: the Docker client in tests moved to
moby/moby,postcss-selector-parserbumped — the Dependabot alerts are closed. - #150 Added an independent Linux CI job for the complete inbound suite with the race detector, including concurrent subtests in one process. Logs are preserved as artifacts; protocol-isolated checks remain enabled.
Removed legacy
Section titled “Removed legacy”- #7 Removed SSR, Hysteria v1, TUIC v4, Shadowsocks stream ciphers and v2ray-plugin.
- #38 The shared TLS/WS/H2 transport moved out of
transport/vmessintotransport/v2ray. - #39 Removed VMess, mKCP, mekya and TLSMirror.
Xray and post-quantum
Section titled “Xray and post-quantum”- #8 Post-quantum REALITY client for Xray 26.9.8+: ML-KEM by default, ML-DSA-65,
client-version. - #9 XHTTP: faster packet-up through CDNs, Xray-compatible padding, path query and xmux defaults.
- #10 Post-quantum REALITY server and Firefox 148 / Safari 26.3 fingerprints (getmikan/utls).
- #19 ECH: Xray
echServerKeysinech-key,ech-opts.query-serverand Xrayech=link forms. - #20 XHTTP server accepts packet-up with any method and data placement like Xray;
server-max-header-bytes. - #21 HTTP/2 inbounds send the same first SETTINGS frame as Caddy (getmikan/http).
- #34 XHTTP: Xray’s default XMUX when
reuse-settingsis empty, reuse manager fixes. - #139 XDrive for VLESS clients and servers: local, Google Drive and template API storage, VLESS Encryption, Mux.Cool and XUDP; Xray interoperability and race checks in CI.
VPN service features
Section titled “VPN service features”- #1 TrustTunnel: a
fallbacksite for requests without valid credentials (from Kinkan). - #5 Events for rejected authentication on inbound listeners (
authevent, from Kinkan). - #36 TrustTunnel: cover-site visits aren’t reported as authentication events.
- #41 Multi-user Shadowsocks 2022 inbound (EIH) with per-user identity.
- #42 Per-user traffic and online IPs in the core, REST and Go API.
- #43 Inbound users changed at runtime: REST
/inbounds/{name}/users, in-place reload. - #44 Per-user limits in the core: speed, connections, IPs, quota, expiry.
- #47 Prometheus
/metricsand/healthz,/readyzprobes. - #63 DNSCrypt v2 resolver for
sdns://stamps. - #64
PUT /rules: replace rules at runtime. - #68 TCP stream listeners on a unix socket.
- #70
GET /events, a push stream of core events. - #71 TrustTunnel: per-proxy HTTP/2 session timers.
- #73
type: chaingroups for cascades, with a health check that names the failed hop. - #136 PROXY protocol v1 and v2 on the TCP inbounds (
proxy-protocol,proxy-protocol-trusted): the client address from the header of a front end such as an SNI router on 443. - #146 Default User-Agent
clash.meta/<version> citrus/<version>: panels still recognise mihomo by the start of the string, Citrus stands out by thecitrus/token. - #147 Added
core: "citrus"to controllerGET /version, preserving the existingmetaandversionfields for panels. - #151 Limits:
speed-group, one speed shared by several users on any inbounds; a new speed without resetting the bucket;SetLimitsfor many limits at once andSetOnLeavefor traffic by destination in an embedding application.
Performance
Section titled “Performance”- #30 Snell v4: buffered reader, about 90× fewer read syscalls.
- #52 Idle connections don’t pin a 32 KB buffer: −30 KB heap per connection.
- #53 XHTTP: half the CPU per GB on h2 downloads (getmikan/http).
- #118 New connections: fewer GC cycles and allocations per connection (GC ballast in server builds only).
- #119 Upload: batched writes with read-ahead, up to +50% on REALITY, Trojan and SS2022, −34% CPU per GB.
- #124 Download: 64 KiB reads, TLS records sent to the socket together (getmikan/tls, getmikan/utls): +40–50% with 32 streams, −25–30% latency under load.
- #125 Hysteria2 and TUIC: less work per stream, the read buffer only when needed, no first-bytes wait goroutine for DIRECT.
- #129 Idle tunnel memory: Trojan −36%, AnyTLS −18%, REALITY −12% (getmikan/tls: read buffer sized by what is moving,
WaitRead). - #131 Latency under load: the relay loop yields after 256 KiB and 1 ms; an echo next to eight downloads 55–63 → 20 ms.
- #130 AnyTLS: received frames queued on their stream, outgoing frames built in place; with 32 streams download is twice as fast, upload +23%, CPU per GB nearly halved; the client race is gone.
Hardening
Section titled “Hardening”- #14 Controller: owner-only unix socket and named pipe, secret on every listener,
/debugbehind auth. - #15 TLS 1.2 minimum, verified Hysteria2 masquerade, owner-only files, redacted DoH credentials.
- #16 Bounds on remote downloads, archive extraction, DoH bodies and binary rule/geodata parsers.
- #17 ShadowTLS: no fallback to private or local addresses for unauthenticated clients.
- #18 SOCKS5 UDP only for authenticated UDP ASSOCIATE and allowed sources.
- #22
tcp-concurrent: bounded fan-out, losing dials cancelled. - #23 DIRECT dials the address the IP rules checked.
- #26 Bounded pre-auth state: VLESS 0-RTT tickets, XHTTP sessions without data.
- #37 Handshake deadline before authentication: VLESS, Trojan, AnyTLS, HTTP transports.
- #55 getmikan/utls v1.8.8-citrus.2: TLS library bug fixes.
- #56 Code review batch: mmdb, share-link cert verify, obfs/Sudoku limits, listener deadlines, regexp timeouts.
- #58 Go 1.26 TLS security fixes (ECH CVE-2026-97031, KeyUpdate) via getmikan/tls and getmikan/utls.
Audit fixes
Section titled “Audit fixes”- #75 CI: actions and images pinned to digests, least-privilege tokens.
- #76 Bounded peer-declared frame lengths (gun, MASQUE h2, TrustTunnel UDP, VLESS addons).
- #77 Updater verifies core packages against
checksums.txtand ignores names inside packages. - #78 Symlink-aware safe paths, atomic resource writes, no redirects into private networks.
- #79 Controller: secret on network addresses, browser guard, reload of controller settings.
- #80 A reload without
forceapplies the inbound access policy. - #81 Sudoku: bounded replay cache, mux streams and http-mask uploads.
- #82 Credentials and core files kept out of reach of the API.
- #83 Providers: bounded
override-expr, provider size and logic nesting; atomic rule-set swap. - #84 Snell and Shadowsocks: handshake deadline before authentication.
- #85 HTTP proxy authenticates every request on a keep-alive connection.
- #86 Global authentication sessions revoked on reload; empty usernames refused.
- #87 SSH host-key warning and opt-out; OpenVPN server identity checks.
- #88 SOCKS5: UDP associations bound to their user and source port.
- #89 Bounded NTP clock steps; DNS cache keyed by client subnet.
- #90 AnyTLS and Trojan: capped streams per mux session.
- #91 QUIC: bounded stream limits for TUIC, ShadowQUIC and TrustTunnel; TUIC pre-auth budget.
- #92 QUIC: bounded TUIC fragment reassembly and ShadowQUIC unbound association state.
- #93 UDP sessions keyed by inbound and user, not only by source.
- #94 Keys and credentials kept out of logs;
age decryptwrites its file with mode 0600. - #95 Snell: the user verified by ShadowTLS/JLS wins over the client ID.
- #96 Hysteria2 Realm: IP header only from
trusted-proxies; session tokens kept out of logs. - #97 XHTTP: sessions stay in the pre-auth limits until VLESS authenticates them.
- #102 Snell: the client ID no longer becomes the user; without ShadowTLS or JLS the inbound has no user.
- #103 DNS listener: in-flight queries capped, each with a deadline.
- #104 Hysteria2 Realm: one events stream per session;
Closeends the active connections. - #105 Providers: the health-check URL is logged without its password and query values.
- #107 CI: the remaining actions and fpm pinned; Go toolchains checked against SHA-256.
- #111 AnyTLS: the peer version from session settings is parsed with a bounds check, so
-1no longer turns on v2 behaviour (found by CodeQL). - #113 AnyTLS: bounded padding scheme and number parsing;
compressanddhcpbumped. - #140 Bounded VLESS replay history and aggregate Sudoku HTTP-mask upload buffers; 64 KiB Sudoku write chunks; invalid ML-DSA seeds are omitted from errors.
- #141 Updated uTLS and ResTLS forks in all three modules: REALITY deadlines until classification, exact fallback limiter threshold, no AuthKey/short ID in logs, cached X25519 keys/buffers, JLS verification callbacks and atomic ResTLS counters.
- #142 Bounded pending REALITY/ShadowTLS/JLS/ResTLS handshakes, ShadowTLS records, Sudoku fallback and KCPTun sessions/streams; server QUIC 0-RTT for TUIC/TrustTunnel is opt-in. Added regression and protocol-isolated inbound race tests; documented compatibility limitations without claiming Sudoku key-auth fixed.
- #143 Controller WebSockets use scoped single-use tickets (30 seconds, bounded storage); reusable URL secrets require an explicit compatibility option. Added migration instructions and hub race coverage.
- #145 Updated controller/events guides for one-time WebSocket tickets and documented package/image updates for the non-root server profile.
- #148 Updated ResTLS in all three modules: fixed a generic QUIC/TLS resumption debug-call panic; the fork now runs its complete TLS/ResTLS regression suite on Go 1.20/1.24/1.26, Windows and under the race detector.
Ported from mihomo and the community
Section titled “Ported from mihomo and the community”- #27 WireGuard: one device per key across reloads, devices of removed proxies closed.
- #28 Don’t retry dials that failed on resource exhaustion.
- #29 Configurable
tcp-connect-timeout. - #31 DNS:
serve-expiredcontrols (defaults keep the current behaviour). - #32
trusted-proxy-headerfor VLESS/Trojan, only fromtrusted-proxies. - #33 BitTorrent sniffer (TCP, uTP, DHT, tracker) and the
PROTOCOLrule. - #60 Hosts: bounded domain mapping chains, followed by the core resolver.
- #61 url-test keeps the current node across provider updates.
- #62 Small ports: TUIC
Close,disable-mtu-discovery, memory limit, sniffed domain in logs. - #65 Small ports 2: JSON logs,
sniffer.skip-threshold, fake-IP for escaped names, OpenVPNtls-version-min. - #66 Tailscale:
dialer-proxyworks, and no UDP goes around it. - #117 Sync with mihomo as of 17 October 2026.
Bug and test fixes
Section titled “Bug and test fixes”- #11 Data races in Vision and AnyTLS.
- #35 Interop tests wait for Citrus to be ready before sending traffic.
- #40 TrustTunnel tests: flaky same-port TCP+UDP binds on Windows.
- #45 Flaky
TestBindRace. - #46 Stats: count relayed bytes whose write reports an error (h2mux undercount).
- #48 Flaky
TestInboundRuntimeUsersConcurrent(loopback detector, TIME_WAIT). - #49 Loopback detector matches DIRECT loops by the whole connection, not the source port.
- #50 Data races in the AnyTLS client session.
- #54 TrustTunnel client: bounded connections, dead HTTP/2 sessions closed.
- #57 Warning about the Windows UDP stack corruption in Go 1.26.0 and 1.26.1.
- #59 Flaky
TestTrackerSpeedLimit. - #67 SOCKS tests: Windows UDP port flake.
- #69 TrustTunnel server drops sessions of clients that vanished.
- #72 XHTTP: a passed deadline no longer tears down an idle conn; early user expiry; test races.
- #100 Flaky Trojan mux cap, TUIC pre-auth budget and TrustTunnel upload tests.
- #106 VLESS link redaction test skips unrelated log lines.
- #115 Flaky tests: AnyTLS interop with mihomo and the stalled TrustTunnel upload.
- #116 Limits: a late close of a user over its quota spares its new connections.
- #122 SOCKS UDP user test takes a free TCP port (Windows).
- #123 smux replaced by the getmikan/smux fork: the caller’s buffer is no longer read after
Writereturns (its bytes could go out on another connection). - #125 TUIC: a
CONNECTto a 255-byte domain no longer crashes the server before authentication. - #137 PROXY protocol: a v1 line shorter than the signature is rejected at once, without reading client data.
- #138 The SOCKS UDP user test takes a port free for both TCP and UDP (Windows).
Citrus replaces five mihomo libraries with its own forks through replace in go.mod. Module paths stay the same; changes go through PRs and CI on dev, and releases are published separately.
-
v1.8.9-0.20261020173537-32502cba39f4: REALITY/JLS fixes from fork PR 3, pinned in #141; adevcommit, not a release tag. -
v1.8.8-citrus.1: the server side of post-quantum REALITY (ML-DSA-65 signature, X25519MLKEM768 requirement), Firefox 148 and Safari 26.3 fingerprints (#10). -
v1.8.8-citrus.2: bug fixes from a code review: session resumption, ECH, server minimum version, compressed certificates, HKDF (#55). -
v1.8.8-citrus.3: security fixes from Go 1.26: ECH outer extension validation (CVE-2026-97031) and KeyUpdate (#58). -
After
v1.8.8-citrus.3,devcommits: the records of one write go to the socket together (#124).
v0.1.8-citrus.1: the same ECH fix (CVE-2026-97031) for TLS inbounds (#58).- After
v0.1.8-citrus.1,devcommits: the records of one write go to the socket together (#124), a read buffer sized by what is moving andConn.WaitRead(#129).
v0.1.8-citrus.1: HTTP/2’s first SETTINGS frame as Caddy sends it (#21).v0.1.8-citrus.2: a 64 KiB HTTP/2 write buffer and batched WINDOW_UPDATE (#53).
- A
devcommit:Writedoes not return while the send loop may still read the caller’s buffer; an abandoned frame is not sent (#123).
v0.1.10-0.20261020174520-8a87bfbeac8c: atomic full-duplex counters and fallback classification notification from fork PR 1, pinned in #141. Fork CI covers the ResTLS protocol; existing failures in the generic TLS suite are not claimed fixed.v0.1.10-0.20261020200739-f74a25fc969b: generic QUIC/TLS resumption panic fix and restored full TLS/ResTLS tests from fork PR 2, pinned in #148. The earlier generic-suite failures are now addressed; no tests are skipped to obtain a pass.
