All notable changes to this project will be documented in this file.
The format follows Keep a Changelog.
Until v1.0 the public API is considered unstable; breaking changes bump
bridgeABI and ship in a minor or patch release.
testGroupsaction: tests the named groups and every group nested in them at once, each under its own test URL; a parent tests a nested group only after that group re-picked. An unpinnedurl-testgroup is then seated on its fastest member: a test the user asked for skips the tolerance, which still keeps background health checks from flapping. A pin stays, unlike mihomo's REST/group/{name}/delay, which clears it, and so does a pin set while the test runs. A reply outside the group'sexpected-statuscounts as a failure. Each group reports as it finishes through agroupDelayevent; the action returns all delays, with a nested group's leaf given the delay its parent picked by.
geo-auto-updatenow runs. mihomo starts its schedule only from its CLImain, so a config asking for it never refreshed the Geo databases here. The loop checks hourly and follows a later config's interval or switch-off.
- A delay test waits up to 30 s for one of the 256 test slots instead of failing at once, so a group test over a big subscription does not report false timeouts.
- Unpinning a node in a
url-testorfallbackgroup. The host app clears a pin by selecting the empty name, which went to mihomo'sSet(); that resolves a name among the members and refuses""withproxy not exist, so the group stayed on the pinned node until the core restarted. An empty name now goes throughForceSet(""), the same split mihomo uses in its own REST handlers. ASelectorgroup is unaffected: it never pins.
golang.org/x/netv0.59.0 -> v0.60.0, which carries the HTTP/2 fixes from GO-2026-6610, -6611, -6612 and -6617. Built against Go 1.27.2 or newer, the core now scans clean.
- DNS query history. The core keeps the last 500 queries that reach mihomo's
resolver.DefaultService: TUN DNS hijack and thednsoutbound. Each entry has domain, query type, answers, rcode or error, latency in ms, and time. The service is wrapped after every config apply, so no mihomo fork is needed. Two new actions forinvokeAction:getDnsQueriesreturns a JSON array, newest first;clearDnsQueriesempties it. Not recorded: thedns.listenserver (it keeps its own service reference) and lookups the core makes for itself (proxy server names, rule matching). JNI surface andbridgeABI(3) unchanged.
- Build toolchain: Kotlin
2.2.10→2.4.20, AGP8.12.2→8.13.2. The facade compiles with language and API version 2.2 (mv=[2,2,0]in class metadata), so hosts on Kotlin 2.2 keep reading it. Kotlin 2.4 warns that Gradle 8.x is deprecated; moving to Gradle 9 needs AGP 9 and is left for a separate change. - CI:
actions/setup-javav4→v6.0.1,gradle/actions/setup-gradlev4.3.1→v5.0.2, both pinned by commit SHA.setup-gradlev6 is not taken: its caching moved to a proprietary component under separate terms. - CI runs
go test -raceon thednsquerypackage.
- Bumped bundled metacubex/mihomo
v1.19.31→v1.19.32(16 upstream commits). No CVE fixes this time. The client-path fixes: a nil dereference when the context is cancelled while an h2 connection is being set up, half-close in sing-mux, the effective MSS now accounts for TCP options, and a race in anytls idle-session cleanup. The converter passes xhttpextra.headersthrough. Additive:hash-keyfor load-balance groups and acongestion-controlleroption for tun. Upstream changed the default tun stack tomips;startTUNalways takes the stack from itsstackargument, so callers keep the stack they pass. Dependencies that come along:sing-tun0.4.24→0.4.27,sing0.5.7→0.5.8,sing-mux0.3.10→0.3.12,utls1.8.7→1.8.8,mieru3.37.0→3.38.0, plusgvisor,mipstack,metacubex/http,metacubex/cpuandnetipx.golang.org/x/*stay at the 0.3.5 versions; a source-modegovulncheckof the android/arm64 build reports nothing reachable. JNI/facade surface andbridgeABI(3) unchanged; all three ABIs rebuild clean and export the expected 11 symbols.
Same mihomo v1.19.31 as 0.3.4; toolchain, dependency and packaging fixes.
golang.org/x/net0.35.0→0.59.0andgolang.org/x/text0.22.0→0.42.0(draggingx/cryptoto0.57.0andx/sys,x/sync,x/termalong). A source-modegovulncheckof the android/arm64 build reached four advisories through our call graph: GO-2026-5026, GO-2026-4918 (HTTP/2 transport infinite loop on a badSETTINGS_MAX_FRAME_SIZE), GO-2025-3503 (proxy bypass via IPv6 zone IDs) and GO-2026-5970 (x/textinfinite loop). It now reports none reachable. These modules sat at mihomo's go1.20-era pins in every earlier release.
- Built with Go
1.27(was1.25, which left support when 1.27 shipped).go.modnow declaresgo 1.26.0, the floor the newgolang.org/x/*require, and pinsgodebug default=go1.20so runtime GODEBUG defaults (TLS, x509, net/http) stay at the level upstream mihomo builds with. - The EasyTier outbound is compiled out (
no_easytier), same as Tailscale (no_tailscale): 0.3.4 grewlibclash.soby ~11 MB per ABI for a mesh-VPN outbound nothing on our side uses, most of it an embedded 6.9 MB WebAssembly core plus the wazero runtime. Aneasytierproxy in a config is now rejected with a "disabled by build tag" error, as atailscaleone already was. build-native.shdefaults to the release tag set (with_gvisor,cmfa,no_tailscale,no_easytier), so CI and local builds compile what ships. Until now only the release workflow dropped Tailscale.
- The CI
go-vulnjob scanned nothing. Every Go file is gated by//go:build android && cgo, and the job ran on the host GOOS, sogovulncheck ./...loaded zero packages and passed vacuously. It now scans the android/arm64 build with the release tags, withgovulncheckpinned tov1.8.0so an upstream release cannot again demand a newer toolchain mid-cycle. golangci-lintv2.7.2→v2.14.0for the newgoline.
- Bumped bundled metacubex/mihomo
v1.19.30→v1.19.31(51 upstream commits). No CVE fixes this time; the client-path fixes are hysteria v1 UDP handling restored and hysteria2 UDP sessions closed with their connection, a VLESS decryption cleanup panic, a nil dereference in WireGuard init, split-DNS over a tailnet peer failing silently in tsnet mode,DomainSetwildcard matching with overlapping rules, IPv6 URLs in xhttp, and the OpenVPN tls-auth HMAC digest derived fromauthinstead of hard-coded SHA-1. A sweep of "close connection after error handling" fixes across doq, mkcp, snell, kcptun, tuic and sudoku, plus lower gVisor/mipstack memory use. Additive: EasyTier outbound,stack: mipsfor tun (reachable through the existingstackstring ofstartTUN), ZeroTieridentity-secret. New indirect depseasytier-goandmetacubex/wazeroride in with EasyTier; the YAML library moves togo.yaml.in/yaml/v3;sing-tun0.4.22→0.4.24,mieru3.35.0→3.37.0,tailscaleto v1.102.3,gvisor,sing-quic,sing-shadowsocks{,2}andamneziawg-gocome along. JNI/facade surface andbridgeABI(3) unchanged; all three ABIs rebuild clean and export the expected 11 symbols.
- A core crash no longer erases its own traceback.
captureStdFdredirects fd 2 into a pipe drained by a goroutine in the same process, so a fatal signal killed the reader before the runtime's traceback could be forwarded — every segfault reached logcat as nothing but the Zygote'ssignal 11line, with no tombstone (the Go runtime handles the signal itself, so debuggerd never sees it).debug.SetCrashOutputnow points the runtime atcrash.log, written next to the file-log sink and therefore in the consumer's external files dir, where it survives the process and can be pulled withadb.
- Bumped bundled metacubex/mihomo
v1.19.29→v1.19.30(83 upstream commits). Security-relevant: CVE-2026-56862 incrypto/tls(viametacubex/tls0.1.7→0.1.8). Two fixes land directly on our client path — the sing_tun UDP DNS-hijack path sent zero-filled or stale packets whenever a reply's uncompressed size exceededSafeDnsPacketSize(PackBufferreallocating out from under the send buffer), and Tailscale no longer fails to recover after the network comes back. The rest is DNS correctness (echo EDNS0 opt, truncate UDP replies to the client's advertised buffer, DNS initialized before NTP), a large sniffer rework (H2C/QUICv2 sniffing, cross-record ClientHello assembly, coalesced QUIC packets, no more waits on partial reads, connections stay open after a failed sniff), and additive outbounds (ZeroTier, AmneziaWG 3.0/3.1, anytlsclient-metadata, hysteria2handshake-timeout, anip-stackoption for wireguard/masque/openvpn). New indirect depsmetacubex/mipstackandmetacubex/zerotier-goride in with ZeroTier;quic-go0.59→0.61,sing-tun0.4.21→0.4.22,tailscaleto v1.102.2,gvisor,mieru3.35.0,restls-client-go0.1.9andbart0.29.0come along. JNI/facade surface andbridgeABI(3) unchanged; all three ABIs rebuild clean and export the expected 11 symbols. - CI and the release pipeline now pin Go
1.25(was1.23);govulncheckrequires it sincex/vulnv1.6.0.go.modstill declaresgo 1.20.
- Bumped bundled metacubex/mihomo
v1.19.28→v1.19.29. Upstream is predominantly additive protocol work (JLS and restls for vmess/vless/trojan/shadowsocks/anytls/snell, shadowquic outbound and listener, OpenVPN TLS rekey andtls-crypt-v2, anytls 0.0.13), which pulls in two new indirect deps —metacubex/jls-tlsandmetacubex/jls-quic-go— and dropsmetacubex/sing-shadowtlsafter upstream reimplemented shadowtls in-tree.metacubex/tailscalerides along to20260711142031. JNI/facade surface andbridgeABI(3) unchanged; all three ABIs rebuild clean and export the expected 11 symbols.
- Bumped bundled metacubex/mihomo
v1.19.27→v1.19.28. Picks up the crypto/tls fix for CVE-2026-42505 (viametacubex/tls0.1.6→0.1.7) plus several remote-triggerable panic fixes — AmneziaWG receive-path slice-bounds OOB, masque dial panic,rand.IntNon a nil slice, and asing-muxUDP-write bug. Rides alongsing-tun0.4.20→0.4.21,utls1.8.4→1.8.7,sing-mux0.3.10,restls-client-go0.1.8, andmieru3.34.0dep bumps. The rest of upstream is additive outbound/listener features (rematchoutbound, masque h3, openvpn peer-info, snell shadow-tls) outside our client path. ThedualStackDialContextfallback-connection-leak fix touches our outbound dialing. JNI/facade surface andbridgeABI(3) unchanged.
- resolveProcess JNI upcall no longer aborts the
:remoteprocess. On Android 11+ package-visibility filtering the consumer'sgetPackagesForUid()can return an empty array, whose.first()throws across the JNI boundary; the native shim now null/exception-guards thepackageNameupcall (native-lib.cpp) andjni_get_string(jni_helper.cpp), returning an empty string instead of aJNI DETECTED ERROR ... obj == nullSIGABRT crash-loop.
resolveProcessmay now return"<uid>\n<package>"; the Go resolver parses the leading uid intometadata.Uid, reviving mihomo UID-based rule matching. A plain package string (no newline) still works, so the parse is backward compatible.
- Breaking:
bridgeABI2→3. TheresolveProcessreturn-string protocol gained the optionaluid\npackageconvention; a consumer emitting it against an ABI-2.sowould have its package silently misparsed, so the ABI gate refuses the mismatched pairing. Rebuild against the new facade. Bundled mihomo core unchanged (v1.19.27).
startTUNnow takes anmtuparameter (Go export, C header, JNI, and Kotlin facade), so the caller sets the tun interface MTU instead of the hardcoded9000.mtu <= 0falls back to the previous9000default. Lets a consumer tune the MTU down for mobile/encapsulated paths where the jumbo default silently blackholes oversized packets.
- Breaking:
bridgeABI1→2. ThestartTUNnative-boundary signature gained themtuargument, so a consumer built against ABI 1 cannot load this.so(the facade/.soABI check refuses the mismatch). Rebuild against the new facade. Bundled mihomo core is unchanged fromv0.1.5(v1.19.27).
- Bumped bundled metacubex/mihomo
v1.19.26→v1.19.27. Security release: fixes several remote-triggerable core crashes — QUIC sniffer out-of-bounds read (crash via a single UDP packet), Vision TLS filter OOB via a craftedsession_id, Trojan UDP relay panic, and a socks4 unbounded allocation. The QUIC sniffer fix is the one relevant to our client path. Pulls inage/sevenzip/brotliindirect deps behind upstream's newpath-in-bundlerule-providers andage-secret-keyfeatures. Upstream removedglobal-client-fingerprint(setclient-fingerprinton the proxy instead); not used by our consumers. JNI/facade surface andbridgeABIunchanged.
- Bumped bundled metacubex/mihomo
v1.19.25→v1.19.26. No CVE in this range; substance is OpenVPN/Snell/mieru/Tailscale work outside our outbound path. Rides along sing-tun0.4.20, quic-go, and metacubex/tls0.1.6dep bumps. JNI/facade surface andbridgeABIunchanged. - Added Go build tag
no_tailscaleto the release build (with_gvisor,cmfa,no_tailscale). Drops the unused Tailscale mesh-VPN outbound stack, shrinkinglibclash.soby ~12 MB/ABI (~36 MB across the.aar).
metadata.jsonrelease asset: machine-readable manifest declaring the bundled core (mihomo vX.Y.Z),bridgeABI, ABIs, and AAR SHA-256. The bundled core version is now also in the release title and the README version matrix, so consumers (and the planned in-app version picker) can see which core a wrapper release ships before downloading.
- Bumped bundled metacubex/mihomo
v1.19.24→v1.19.25. Picks up thenet/httpCVE-2026-39825 fix and the vless xhttp h3 quic-dial panic fix; the JNI/facade surface andbridgeABIare unchanged.
Initial public release.
- JNI bridge to metacubex/mihomo
v1.19.24, statically linked intolibclash.soper ABI (arm64-v8a, armeabi-v7a, x86_64), built with Go tagswith_gvisor,cmfa. libmihomo-jni.soper ABI, built via Gradle CMake fromsrc/main/cpp/{native-lib.cpp, jni_helper.cpp}. HoldsJNI_OnLoadthat wires up the bridge callback function pointers.- Kotlin facade
io.github.oviron.libmihomo.Clashwith 11 entry points (invokeAction,quickSetup,startTUN,stopTun,setEventListener,getTraffic,getTotalTraffic,suspended,forceGC,updateDNS,bridgeABI) and lambda-friendly overloads. - Typed callback interfaces
TunInterface(protect,resolverProcess) andInvokeInterface(onResult). Clash.load(nativeLibDir: String)explicit load step viaSystem.load(absolutePath). The path argument is the seam for runtime-pluggable versions.Clash.isLoaded()/Clash.assertReady()for explicit load-state introspection. Every facade method invokesassertReady().bridgeABI() = 1runtime constant for facade ↔.socompat check.consumer-rules.procoversClash+TunInterface+InvokeInterface+ native-method wildcard so consumer R8 cannot strip JNI-referenced classes.scripts/validate-jni-keep.shdiffs JNI lookups in C/C++ sources against-keeprules. Wired into GradlepreBuild, so any build with mismatched coverage fails before a tag is cut.assembleReleasepackages all three ABIs plus the Kotlin facade into a single.aar. Release artifacts:.aar+.aar.sha256+.aar.asc(detached GPG signature, key fingerprint1139 C91B 6525 883E 6783 DCF0 4A94 DA48 8A4C 5033).
- Compiled binary is GPL-3.0 (mihomo static link). Bridge source in this repo is also GPL-3.0.
- Pinned toolchain: Go 1.20+ runtime, NDK
28.0.13004108, AGP8.12.2, Kotlin2.2.10.