Files
3x-ui/internal
ilyusha fc05249e0c fix(geofile): verify downloaded geo databases against published digests (#6404)
* fix(geofile): verify downloaded geo databases against published digests

UpdateGeofile wrote whatever the three upstreams returned straight into the
Xray asset folder with no integrity check. Xray parses these databases when it
builds its routing matchers, so a corrupted or substituted file takes the core
down at its next start.

The panel already does this for the other artifact it downloads: installXray
checks the release archive against the SHA-256 published in its .dgst sidecar.
The geo databases were the one download that skipped it, even though all three
upstreams publish a <asset>.sha256sum beside every .dat.

Fetch that sidecar, compare it against the bytes that actually arrived, and
stage every file in a temporary folder first, so one bad database installs
nothing rather than leaving the core running databases from two releases.

Match the digest line by base name rather than by the path it records.
Loyalsoldier and runetfreedom write "<hash>  geoip.dat" while chocolate4u
writes "<hash>  release/geoip.dat" -- the path from its own build -- so
`sha256sum --check` semantics fail on a perfectly good download.

Also skip the Xray restart when every upstream answered 304. The conditional
GET was already there, but the restart ran unconditionally and dropped every
client connection on a refresh that changed nothing.

Assisted-by: Claude Code:claude-opus-5 (mostly)

* fix(geofile): pin the release and scope atomicity to one upstream

Four corrections to the digest verification, all from review.

Pin the release. The asset and its .sha256sum were fetched as two independent
requests to releases/latest/download/, so GitHub re-resolved "latest" between
them. These upstreams publish several times a day -- 202609022346, 202609030908
and 202609031849 are three tags from one day -- so a release landing mid-batch
had release N+1's digest checked against release N's bytes, reporting a healthy
upstream as "corrupted or tampered with". Resolve the tag once per upstream from
the redirect GitHub already returns, then fetch body and digest from it. Modeling
the entry as repo + asset rather than an opaque URL is what makes that possible.

Scope atomicity to one upstream. A single failure discarded every verified
download, so one transient 5xx from one of three independent repositories threw
away four good files and re-downloaded tens of MB on the next attempt. The
integrity argument holds for a geoip/geosite pair out of one release; across
repositories it buys nothing. Each upstream now installs or aborts on its own
and errors are collected, as the code did before this feature.

Make the all-or-none test deterministic. It ranged a map, so when the corrupt
entry came first the run returned before the good file was ever requested and
the assertions held trivially -- a coin flip that would also pass against an
implementation installing each file as it verified. Iteration is sorted now, and
the test asserts the good file was actually downloaded first.

Assert which error. The error table checked only that err != nil, so its two
branches could swallow each other's cases; each row now pins the message. Also
trims three comment blocks to the two-line limit.

Assisted-by: Claude Code:claude-opus-5 (mostly)
2026-09-05 20:49:15 +02:00
..
2026-08-24 15:07:15 +02:00