[Bug]: Preview PortScanner sends HTTP(S) to unrelated listeners and can crash other apps
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Run T3 Code Alpha 0.0.34 on macOS.
- In Settings → General → Legacy features, enable Sidebar (legacy). Browser Preview does not need to be open.
- Leave T3 Code running with the legacy sidebar mounted.
PortDiscovery.pollTickcontinues approximately every three seconds because the sidebar subscribes to environment-wide discovered servers. - Start a local non-HTTP service that expects a binary handshake. The reproduced service is the private JxBrowser RPC listener created during thinkorswim login.
- T3 Code's
PortScannerdiscovers the listener through the system-widelsofscan and sendsGET /first over HTTP and then HTTPS. - Observe the foreign-protocol connection in T3's trace and the receiving application's failure.
An alternative retainer is an empty Browser Preview surface, which also subscribes to discovered servers.
The thinkorswim reproduction is intermittent because its JxBrowser listener uses a random loopback port and the failure depends on timing during browser startup. It has occurred repeatedly during login after reboot/update.
Expected behavior
Browser Preview discovery should not write HTTP protocol bytes to arbitrary system-wide TCP listeners. Active probes should be limited to T3-managed process trees, explicitly configured Preview URLs, or another opt-in/safe eligibility boundary.
Actual behavior
On macOS/Linux, PortScanner enumerates all listening TCP sockets. Terminal ownership is recorded as metadata but is not an eligibility filter. For every discovered listener, T3 constructs HTTP and HTTPS URLs and performs GET requests.
Preview does not need to be open. The legacy sidebar invokes useThreadDiscoveredPorts for displayed threads, which maps to the environment-wide previewEnvironment.discoveredServers query. With legacySidebarEnabled: true, a live trace showed PortDiscovery.pollTick approximately every three seconds while no Preview interaction was occurring. The query layer may deduplicate identical environment subscriptions, but the mounted legacy sidebar still retains at least one discovery subscription.
This behavior was introduced by #6021 to distinguish browser-ready servers from services such as MySQL and Redis. A listener must therefore receive HTTP traffic before T3 decides that it is not an HTTP service.
In the captured reproduction, T3 probed thinkorswim's JxBrowser 9.0.1 binary RPC listener on port 63261. thinkorswim then raised a NullPointerException in the connection handshake and hung during login.
Relevant source:
- Legacy-sidebar retention path: https://github.com/pingdotgg/t3code/blob/9fd788b5a92254a2afa72c2a53513ed2cb730f0d/apps/web/src/components/LegacySidebar.tsx#L374-L386
- Discovery-query mapping: https://github.com/pingdotgg/t3code/blob/9fd788b5a92254a2afa72c2a53513ed2cb730f0d/apps/web/src/portDiscoveryState.ts#L53-L65
- WebSocket retain/scan subscription: https://github.com/pingdotgg/t3code/blob/9fd788b5a92254a2afa72c2a53513ed2cb730f0d/apps/server/src/ws.ts#L2159-L2184
- Empty-Preview retention path: https://github.com/pingdotgg/t3code/blob/9fd788b5a92254a2afa72c2a53513ed2cb730f0d/apps/web/src/features/preview/PreviewEmptyState.tsx#L20-L31
- Listener enumeration: https://github.com/pingdotgg/t3code/blob/9fd788b5a92254a2afa72c2a53513ed2cb730f0d/apps/server/src/preview/PortScanner.ts#L480-L535
- HTTP/HTTPS candidate construction: https://github.com/pingdotgg/t3code/blob/9fd788b5a92254a2afa72c2a53513ed2cb730f0d/apps/server/src/preview/PortScanner.ts#L350-L390
- HTTP
GETclassifier: https://github.com/pingdotgg/t3code/blob/9fd788b5a92254a2afa72c2a53513ed2cb730f0d/apps/server/src/preview/PortScanner.ts#L329-L348 - Three-second polling: https://github.com/pingdotgg/t3code/blob/9fd788b5a92254a2afa72c2a53513ed2cb730f0d/apps/server/src/preview/PortScanner.ts#L550-L583
Impact
Major degradation or frequent failure. The scanner can be a hidden background side effect of ordinary T3 use rather than only an effect of explicitly using Browser Preview.
Version or commit
T3 Code Alpha 0.0.34; behavior introduced by merged PR #6021 / commit 9fd788b5a92254a2afa72c2a53513ed2cb730f0d
Environment
- macOS 26.6.2, Apple Silicon
- T3 Code Alpha 0.0.34
- thinkorswim build 1993.0.48
- Java 21.0.5 aarch64
- JxBrowser 9.0.1 / Chromium 147.0.7727.138
Logs or stack traces
T3 server trace (EDT):
2026-08-27 09:32:07.068 PortDiscovery http.client GET http://localhost:63261/
2026-08-27 09:32:08.070 HTTP probe ends after 1001.755 ms; HTTPS probe begins
thinkorswim client.log (EDT):
2026-08-27 09:32:07.212 OAuth logged in
2026-08-27 09:32:08.079 Error in IPC Server Thread
java.lang.NullPointerException: Cannot invoke
"com.teamdev.jxbrowser.internal.rpc.ConnectionCreated.getConnectionData()"
because "connectionCreated" is null
at com.teamdev.jxbrowser.internal.rpc.transport.ConnectionServer$AcceptConnections.acceptConnection(ConnectionServer.java:191)
at com.teamdev.jxbrowser.internal.rpc.transport.ConnectionServer$AcceptConnections.run(ConnectionServer.java:170)
at java.base/java.lang.Thread.run(Unknown Source)
Runtime socket inspection confirmed that the T3 server process was connected to the same JxBrowser listener port. The synchronized timestamps establish T3's probe as the trigger; the null dereference itself is also a JxBrowser input-validation defect.
Workaround
Disable Settings → General → Legacy features → Sidebar (legacy), close any empty Browser Preview surfaces, and wait for discovery retainers to release before launching thinkorswim. Quitting T3 Code during thinkorswim startup is a fallback. A per-process outbound firewall rule preventing T3 from connecting to random high-numbered loopback ports is another containment option. These measures do not fix the root cause.
Related but non-fixing work: #4187, #5900, #6254, and #7789.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with apps/server/src/preview/PortScanner.ts, especially listener enumeration, candidate construction, HTTP classification, and polling, then trace the retainers through apps/server/src/ws.ts, apps/web/src/portDiscoveryState.ts, LegacySidebar.tsx, and PreviewEmptyState.tsx. Reproduce with the legacy sidebar and a non-HTTP listener; done means unrelated listeners receive no HTTP probes while the intended Preview discovery behavior remains covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100