pingdotgg / pingdotgg/t3code

[Bug]: Preview PortScanner sends HTTP(S) to unrelated listeners and can crash other apps

Open
#8,407 3 comments 2 reactions 0 assignees View on GitHub

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

  1. Run T3 Code Alpha 0.0.34 on macOS.
  2. In Settings → General → Legacy features, enable Sidebar (legacy). Browser Preview does not need to be open.
  3. Leave T3 Code running with the legacy sidebar mounted. PortDiscovery.pollTick continues approximately every three seconds because the sidebar subscribes to environment-wide discovered servers.
  4. Start a local non-HTTP service that expects a binary handshake. The reproduced service is the private JxBrowser RPC listener created during thinkorswim login.
  5. T3 Code's PortScanner discovers the listener through the system-wide lsof scan and sends GET / first over HTTP and then HTTPS.
  6. 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:

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.