Research/t3code-websocket-ticket-origin
0-dayNo CVELowPublic

T3 Code, wsTicket upgrade ignores Origin

T3 Code 61c3794c authenticateWebSocketUpgrade verifies a wsTicket without an Origin check. Cookie sessions with a hostile Origin are rejected. Cookie sessions with no Origin are accepted. SameSite=Lax. Labbed. No CVE yet.

Name
T3 Code, wsTicket upgrade ignores Origin
Type
0-day analysis
CVE
n/a
CVE Risk
low
Disclosure Status
public
Vendor
Ping
Affected
T3 Code pingdotgg/t3code 61c3794c32b0eeebfac09add43e403618c65d2d7. Needs a live wsTicket or a cookie client that omits Origin.
Published
03 Nov 2026
Updated
03 Nov 2026
Tags
0-day, t3code, csrf, origin, cwe-346, cwe-200, local

The ticket in the URL did not ask which site called.

I am @abraxas_null. The proof of concept is on GitHub: abraxas/t3code-websocket-ticket-origin (loopback client). The lab is lab/: run.sh, poc.py, harness.test.ts. Authorized lab only. Production authenticateWebSocketUpgrade on the Effect test layer. No live browser CSRF.

This is T3 Code commit 61c3794c. No CVE yet. CWE-346 / CWE-200. 3.1 Low (CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N). Origin leftover. Ticket holder, or a cookie client that omits Origin.

A socket ticket in the URL does not care which website asked for it. Cookie sessions with a hostile Origin are rejected. Cookie sessions with no Origin header are accepted. Bearer tokens ignore Origin on purpose. The session cookie is SameSite=Lax, which is what currently stops a cross-site cookie WebSocket.

This is not unauthenticated /ws. This is not a cookie CSRF in current browsers when Origin is present.

Why this leftover, and what the audit overstated

GET /ws in ws.ts calls authenticateWebSocketUpgrade(request, "rpc"). If the query has wsTicket, EnvironmentAuth.ts verifies the ticket (and DPoP when bound) and returns without isBrowserOriginAllowed. Cookie fallback goes through authenticateRequest, which does call it for cookie credentials.

isBrowserOriginAllowed returns true when Origin is missing. Otherwise it requires same-origin Sec-Fetch-Site, a trusted app origin, or a host match against Host / X-Forwarded-Host. In-tree tests already accept cookie upgrades from the app's own origins, accept a missing Origin, and reject origin: null and a wrong port. Bearer upgrades succeed regardless of page origin.

The leftover is the ticket path and the missing-Origin cookie branch. Cookie plus hostile Origin is closed on this pin.

Pinning 61c3794c

Same audit pin. I copied the EnvironmentAuth test layer, minted a pairing cookie session and a wsTicket, and sent the Origin matrix through the production authenticator.

The skip list

I did not drive a real browser. SameSite=Lax is doing the CSRF job for cookies. I did not treat Bearer-from-anywhere as a leftover; that is the non-browser client path.

What an attacker can do

Needs a live wsTicket or a cookie the browser will attach.

Privilege Method / sink Recon / integrity / availability Lab-proven or inferred
Ticket upgrade with hostile Origin authenticateWebSocketUpgrade wsTicket Integrity Lab-proven. accepted, Origin https://evil.example, witness t3f04o6n1
Cookie upgrade with no Origin isBrowserOriginAllowed Integrity Lab-proven. accepted
Bearer upgrade with hostile Origin Authorization header Integrity Lab-proven. accepted
Negative control: cookie + hostile Origin Origin other port n/a Lab-proven. ServerAuthInvalidCredentialError
Negative control: cookie + trusted Origin same host n/a Lab-proven. accepted

Stays closed on this leftover

Axis Method / sink Why it is closed
Integrity Cookie WebSocket from a random site that sends Origin rejected; SameSite=Lax also withholds the cookie
Integrity Unauthenticated /ws missing credential
Integrity Cross-site cookie CSRF in current browsers sameSite: "lax" in apps/server/src/auth/http.ts

The lab

Plain text
IOC cookie-missing-origin=accepted
IOC cookie-hostile-origin=rejected
IOC cookie-trusted-origin=accepted
IOC ticket-hostile-origin=accepted
IOC bearer-hostile-origin=accepted
IOC samesite=lax
SUCCESS T3-F04-WSORIGIN t3f04o6n1

Wrong turns: reporting "no Origin check anywhere" for cookie upgrades.

The fix

Run isBrowserOriginAllowed on the ticket path, or bind tickets to the Origin that minted them. Treat a missing Origin as a failure for cookie credentials. Add an explicit Origin allowlist on the upgrade. Keep SameSite=Lax or Strict.

I am @abraxas_null. Site abraxaslabs.tech. GitHub abraxas. Mail abraxas.null@proton.me.