Skip to content

[Bug]: Desktop (Tauri) bundle ships an OpenSSL 3.0.x-era TLS stack (Python 3.11) — carrier DPI resets the handshakes; bump desktop CI to Python 3.13 #7298

Description

@LUOSENGWA

QwenPaw Version

2.1.0

Description

The desktop (Tauri) bundle ships a Python 3.11 runtime, which bundles OpenSSL 3.0.x.
On some carrier networks, TLS handshakes made with that stack to self-hosted HTTPS endpoints are
reset by a middlebox (injected RST); the same endpoints work fine from modern stacks
(browser, curl/OpenSSL 3.5.x). Desktop users cannot change the bundled runtime (frozen stdlib,
no override hook), so the only fix is to bump the desktop build pipeline to Python 3.13
(bundles OpenSSL 3.5.x). Python 3.12 is not an option: Windows Python 3.12 still bundles
OpenSSL 3.0.x — the exact fingerprint being blocked.

Symptom

  • Desktop app (Windows), selfcheck-reported stack Python 3.11.9 / OpenSSL 3.0.13: TLS handshake
    to a self-hosted HTTPS endpoint (cloud VPS + reverse proxy + public CA cert) fails after ~100 ms
    with a connection reset (WinError 10054).
  • Same codebase in the official Docker image (Python 3.11.2 / OpenSSL 3.0.20):
    ssl.SSLError: TLS/SSL connection has been closed (EOF) (SSLZeroReturnError) — same endpoint, same network.
  • From the same machine / same network, modern stacks succeed: host curl (OpenSSL 3.5.5) ✅,
    a Python 3.13 + OpenSSL 3.5.6 build of this codebase → TLSv1.3 ✅, phone browser ✅.

Root cause: middlebox RST injection, not a server fault

Server-side tcpdump (at the VPS) shows the reset is injected in the path:

  1. The same "peer" sends two RSTs with different sequence numbers (one at the pre-data position,
    one at initial-seq + ClientHello length) — a genuine TCP stack cannot own both send positions.
  2. The RSTs carry a TTL 4 hops off the normal packets (55 vs 51) and ToS 0 vs 0x14.
  3. Both RSTs share one IP identification → a single injecting device.
  4. The server access log shows a complete handshake path: certificate selected, ServerHello sent,
    then write: connection reset by peer.

Controlled experiments pin down the trigger dimensions:

Client TLS stack SNI Result
OpenSSL 3.5.x (host Python) self-hosted domain
OpenSSL 3.0.20 (container; default / TLS1.2-only / forced strong ciphers — 3 variants) self-hosted domain ❌ RST
OpenSSL 3.0.20 (same container) unrelated public domain ✅ (reaches the server; normal certificate alert)

→ Blocking requires both conditions: SNI in the target domain family and a legacy
TLS-stack fingerprint
(3.0-era ClientHello ≈ 517 B with legacy cipher/protocol declarations,
vs ≈ 1569 B for modern stacks). It is not a weak-cipher issue (forcing strong ciphers did not
help) and it is not a server issue (server side was verified clean end to end).

Scope note: DPI rule sets differ per carrier and per network — on one 5G network we also saw a
modern mobile-app stack (non-browser) blocked, i.e. some rules are stricter than the 3.0-era
fingerprint. The fix below restores connectivity on the networks where the 3.0-era stack is the
distinguishing factor — which is exactly where desktop users stand today, with no workaround.

Why this needs an upstream fix

Unlike a browser, a desktop user has no way to change the TLS stack: the runtime is frozen
into the bundle (PyInstaller onedir; the stdlib lives in the frozen archive, no env/config hook to
redirect to a system Python). Server-side workarounds (other ports, SNI aliases) do not help — the
device fingerprints the client stack, not just the SNI. The fix is a build-time change only.

Why 3.13 specifically (not 3.12)

  • Windows Python 3.12 still bundles OpenSSL 3.0.x → same blocked fingerprint.
  • Python 3.13 bundles OpenSSL 3.5.x.
  • Measured A/B on the same machine, same network, same target: 3.11 + OpenSSL 3.0.20 → RST;
    3.13 + OpenSSL 3.5.6 → TLSv1.3 OK (this codebase, rebuilt on a 3.13 base).

Proposed change (desktop pipeline only)

python-version bumps in the desktop workflows — the CI build-machine Python is what
scripts/pack-tauri/stage_python_runtime.py follows when selecting the bundled runtime:

File Lines Current
.github/workflows/desktop-build.yml L50, L183 "3.11"
.github/workflows/desktop-release.yml L77, L140, L182, L320, L469 "3.11"
.github/workflows/desktop-publish.yml L77 "3.11"
.github/workflows/desktop-promote.yml L51 "3.11"
.github/workflows/fork-verify-desktop.yml L56, L130 "3.10" (verify workflow should stage the same runtime as the real pipeline)

Line numbers as of upstream/main @ 3f13a7b2.

No other changes required: stage_python_runtime.py already pins python-build-standalone
release 20260623, which ships cpython-3.13.14 for both x86_64-pc-windows-msvc and
aarch64-apple-darwin; the staged runtime version follows the CI python-version automatically.

Compatibility evidence

  • The upstream CI test matrices already run Python 3.13 alongside 3.11
    (tests.yml / full-tests-nightly.yml / release-verify.yml: matrix ["3.11", "3.13"]) —
    the codebase is continuously verified on 3.13.
  • pyproject.toml: requires-python = ">=3.11,<3.14" — 3.13 is inside the supported range.
  • The desktop bundle builds the same codebase; only the runtime/toolchain version changes.

Component(s) Affected

  • Core / Backend (app, agents, config, providers, utils, local_models)
  • Console (frontend web UI)
  • Channels (DingTalk, Feishu, QQ, Discord, iMessage, etc.)
  • Skills
  • CLI
  • Documentation (website)
  • Tests
  • CI/CD
  • Scripts / Deploy

Environment

  • QwenPaw version: [e.g. 0.x.x or git commit]
  • OS: [e.g. macOS 14, Ubuntu 22.04, Windows 11]
  • Install method: [pip / one-line install / Docker / from source]
  • Python version (if applicable): [e.g. 3.10]

Steps to Reproduce

Actual vs Expected

  • Actual:
  • Expected:

Logs / Screenshots

[Paste relevant log output or attach screenshots. Use code blocks for logs.]

(paste logs here)

Additional Notes

[Optional: workarounds, similar issues, etc.]

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions