From 13c04bb982f378883383de740a8974291448e21c Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 1 Jun 2026 17:37:54 +0200 Subject: [PATCH] fix(outbound): fill encryption and pqv when importing VLESS link The link-to-JSON importer dropped two VLESS Reality fields: - pqv (post-quantum ML-DSA-65 verify key) was never parsed; map it back to realitySettings.mldsa65Verify, matching the inbound link generator. - encryption was force-reset to 'none' in the form adapter regardless of the parsed value, discarding post-quantum encryption strings. Add regression tests for both paths. --- .../src/lib/xray/outbound-form-adapter.ts | 2 +- frontend/src/lib/xray/outbound-link-parser.ts | 1 + .../src/test/outbound-form-adapter.test.ts | 19 +++++++++++++++++++ .../src/test/outbound-link-parser.test.ts | 17 +++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/xray/outbound-form-adapter.ts b/frontend/src/lib/xray/outbound-form-adapter.ts index 200f6974..cfee7284 100644 --- a/frontend/src/lib/xray/outbound-form-adapter.ts +++ b/frontend/src/lib/xray/outbound-form-adapter.ts @@ -123,7 +123,7 @@ function vlessFromWire(raw: Raw): VlessOutboundFormSettings { port, id, flow, - encryption: (encryption === 'none' ? 'none' : 'none') as 'none', + encryption: encryption || 'none', reverseTag, reverseSniffing, testpre: asNumber(raw.testpre, 0), diff --git a/frontend/src/lib/xray/outbound-link-parser.ts b/frontend/src/lib/xray/outbound-link-parser.ts index 4a1fdbb2..b887c0c5 100644 --- a/frontend/src/lib/xray/outbound-link-parser.ts +++ b/frontend/src/lib/xray/outbound-link-parser.ts @@ -210,6 +210,7 @@ function applySecurityParams(stream: Raw, params: URLSearchParams): void { reality.publicKey = params.get('pbk') ?? ''; reality.shortId = params.get('sid') ?? ''; reality.spiderX = params.get('spx') ?? ''; + reality.mldsa65Verify = params.get('pqv') ?? ''; } } diff --git a/frontend/src/test/outbound-form-adapter.test.ts b/frontend/src/test/outbound-form-adapter.test.ts index 8fe31f00..772ed9c4 100644 --- a/frontend/src/test/outbound-form-adapter.test.ts +++ b/frontend/src/test/outbound-form-adapter.test.ts @@ -74,6 +74,25 @@ describe('outbound-form-adapter: round-trip', () => { }); }); + it('vless preserves a non-none encryption value (post-quantum)', () => { + const enc = 'mlkem768x25519plus.native.0rtt.G3cdPSd1-NnlpTbWNSM5vHsT5VNzWfFzYSKwbUMnV1Y'; + const wire = { + protocol: 'vless', + settings: { + address: 'srv', + port: 443, + id: '11111111-2222-4333-8444-555555555555', + flow: '', + encryption: enc, + }, + }; + const form = rawOutboundToFormValues(wire); + if (form.protocol === 'vless') { + expect(form.settings.encryption).toBe(enc); + } + expect((formValuesToWirePayload(form).settings as Record).encryption).toBe(enc); + }); + it('vless emits reverse + sniffing when reverseTag is set', () => { const wire = { protocol: 'vless', diff --git a/frontend/src/test/outbound-link-parser.test.ts b/frontend/src/test/outbound-link-parser.test.ts index 6d3c9edb..7c7001dd 100644 --- a/frontend/src/test/outbound-link-parser.test.ts +++ b/frontend/src/test/outbound-link-parser.test.ts @@ -173,6 +173,23 @@ describe('parseVlessLink', () => { expect(reality.shortId).toBe('abcd'); expect(reality.serverName).toBe('cloudflare.com'); }); + + it('parses encryption + pqv (post-quantum) into settings and mldsa65Verify', () => { + const enc = 'mlkem768x25519plus.native.0rtt.G3cdPSd1-NnlpTbWNSM5vHsT5VNzWfFzYSKwbUMnV1Y'; + const pqv = 'GIsemxbGPjDRH1ONfmoGlVkJ4etNuLmYDvzpjmFFreDLd8WjoJxJ4Fmt_NQJaC6'; + const link + = 'vless://9406c224-8ac6-4675-ae0b-f93785959418@localhost:1121' + + `?encryption=${enc}&pqv=${pqv}` + + '&security=reality&sid=29cf418813d5bac7&sni=aws.amazon.com' + + '&pbk=aQaGBOT2hMfXWebYtjADoOVUrP8qZRdwXVap7nrId0I&fp=chrome&spx=%2FOUTjB7xHRiP4zBP&type=tcp' + + '#giqssbgmo9'; + const out = parseVlessLink(link); + const settings = out?.settings as { encryption: string }; + expect(settings.encryption).toBe(enc); + const reality = (out?.streamSettings as Record).realitySettings as Record; + expect(reality.mldsa65Verify).toBe(pqv); + expect(reality.publicKey).toBe('aQaGBOT2hMfXWebYtjADoOVUrP8qZRdwXVap7nrId0I'); + }); }); describe('parseTrojanLink', () => {