fix(link): reject non-finite and clamp out-of-range quicParams from fm=

Follow-up hardening of the fm= sanitizer found in review. ParseFloat
accepts "inf"/"NaN", and a non-finite float64 makes json.Marshal fail
later - the subscription refresh discards that error and blanks the
stored outbound set, so one poisoned link could wipe a subscription's
outbounds. Values that coerce fine but sit outside xray-core's accepted
ranges (keepAlivePeriod 0 or 2-60, maxIdleTimeout 0 or 4-120,
maxIncomingStreams 0 or >= 8) still killed the config load, and huge
magnitudes serialize in exponent notation that xray's integer fields
reject. Coerced values are now stored as integers, clamped into the
accepted ranges, and dropped when negative, non-finite, or absurdly
large; the TS import parser mirrors the same rules.

Refs #5783
This commit is contained in:
MHSanaei
2026-07-05 21:16:56 +02:00
parent 837cf5f24e
commit 0753f5ee83
4 changed files with 156 additions and 36 deletions
@@ -330,6 +330,30 @@ describe('parseHysteria2Link', () => {
expect(quic.brutalUp).toBe('100 mbps');
});
it('clamps quicParams to the ranges xray accepts and drops junk — #5783', () => {
const fm = encodeURIComponent(JSON.stringify({
quicParams: {
keepAlivePeriod: '1s',
maxIdleTimeout: '10m',
maxIncomingStreams: 4,
initStreamReceiveWindow: 'inf',
maxStreamReceiveWindow: -5,
initConnectionReceiveWindow: 1e30,
},
}));
const link = `hysteria2://78e7795a209c4c099f896a816fc8448f@news.domain.org:8443?security=tls&sni=news.domain.org&fm=${fm}#hy2-clamp`;
const out = parseHysteria2Link(link);
expect(out).not.toBeNull();
const finalmask = (out!.streamSettings as Record<string, unknown>).finalmask as Record<string, unknown>;
const quic = finalmask.quicParams as Record<string, unknown>;
expect(quic.keepAlivePeriod).toBe(2);
expect(quic.maxIdleTimeout).toBe(120);
expect(quic.maxIncomingStreams).toBe(8);
expect('initStreamReceiveWindow' in quic).toBe(false);
expect('maxStreamReceiveWindow' in quic).toBe(false);
expect('initConnectionReceiveWindow' in quic).toBe(false);
});
it('round-trips the realm tlsConfig under fm', () => {
const fm = encodeURIComponent(JSON.stringify({
udp: [{