* feat(sub): add legacy Clash subscription endpoint
* fix(deps): update js-yaml to patched release
Raise the Swagger UI js-yaml override to 4.3.2 and refresh the lockfile to resolve GHSA-2883-xcg3-v3hh without changing Swagger UI.
* fix(sub): preserve client detection and normalize legacy cipher
Keep the original Clash/Mihomo auto-detection default so existing subscription URLs continue returning YAML. Normalize the panel-supported chacha20-poly1305 alias when generating legacy Clash profiles, and cover both regressions through HTTP endpoint tests.
* refactor(sub): drop an unreachable guard and make the alias test assert
Review of the legacy Clash subscription endpoint left three LOW findings, all
introduced by the change:
- The comment above the routing merge ran to three lines, over CLAUDE.md's
two-line cap.
- validateClashRouteGraph on the legacy path could never fail: the legacy
branch skips the routing merge, so it validated the literal config built a
few lines above against itself. Dead code that reads as a guard.
- TestClashAliasesSkipConfiguredPathConflicts asserted nothing — it could only
fail on an escaping gin panic, so a regression that registered the alias
handler on the configured path went unnoticed. It now drives each collision
through the router and asserts which format answers each path.
---------
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
An inbound could be saved with security "tls" and a certificate row carrying
neither a file path nor inline content. Nothing rejected it, so the row reached
xray-core, whose readFileOrString fails with "both file and bytes are empty"
and takes the whole config build down with it — every other inbound included.
Validate the credentials on both sides of the wire. validateInboundTLSCertificates
follows xray's file-over-inline precedence, requires a private key for every
non-verify certificate and insists on at least one server certificate, so a
verify-only CA list no longer passes as a server config. The inbound form's Zod
schema enforces the same rules per field and serializes only the editor mode the
operator actually used, and a failed save jumps to the Security tab naming the
certificate row that broke.
On update the guard is scoped to a real TLS edit. A row already stored
incomplete is grandfathered: it stays editable, and only a save that breaks a
previously valid block is refused.
A sub-node stores whatever the master pushes, and Remote.UpdateInbound falls
back to AddInbound when the node does not yet hold the tag, so a grandfathered
row could otherwise never be deployed or re-seeded — the rejection is swallowed
to a logger.Debug line and the node stays on a stale config while the panel
shows the client as cut off. The controller now marks a node-sync request (mTLS
or a node-sync token) on a per-request copy of InboundService, and the guard
steps aside for it on both add and update: the row was judged where the
operator acted, and a node that refuses it only falls out of sync. Operator and
admin-token saves are held to the guard as before.
The security union is parameterised on its tlsSettings branch instead of copied,
and tlsCertUsesFiles is the one file-vs-inline inference shared by the form
schema and the adapter, so the mode the editor opens in and the pair of fields
the save serializes cannot drift apart.