ConnectOnionAll Posts
Remote Browser

The Second Connection Was Still the First

Preserve relay and session continuity across browser reloads while checking that identity and authority have not changed.

By ConnectOnion Team · 4 min read · 727 words

Remote BrowserPermissionsRelease

The Codex run had succeeded. The Work Room showed the same provider session, the follow-up returned the exact confirmation string, and the parent transcript contained both completed cards. Then we reloaded the page and the Host said:

already authenticated: open a new connection

That answer sounded reasonable. The browser had opened a new WebSocket. The surprise was that, from the Host's point of view, it had not opened a new connection at all.

OIP sessions commonly travel through the relay. The relay multiplexes browser traffic to a Host using the application session ID. On reload, the new browser socket can send its signed CONNECT before the close from the old socket has been delivered and processed. Both frames briefly occupy the same logical relay queue. The second physical connection therefore arrives inside the first run_ws_session loop, where the connection is already authenticated.

The existing rule rejected every second CONNECT. It was added for a good reason: an authenticated socket must not replace its caller, downgrade signed commands, point at another recipient, or switch sessions. Removing that guard would fix reload by reopening a security boundary.

The useful distinction was not first versus second. It was change versus reattach.

A reload now gets one narrow path. The Host verifies the fresh signature again, then requires the same caller, recipient, signed-command capability, supported OIP protocol, and application session. If all of those are equal, it republishes CONNECTED and the authenticated profile. It does not start a second forwarder for a running agent. If any field differs, the old rejection remains. Reusing the same signature still fails the replay guard.

The first public alpha taught us that “verifies the fresh signature again” was still too broad. The implementation reused the whole first-connect gate, so it also repeated the mutable trust decision: read the contact lists, decide whether the caller must onboard, and consult policy. A brand-new session connected, but its first Send raced another equivalent CONNECT through that second policy read. A local missing-file error became misconfigured: [Errno 2] in the browser, and the actual INPUT never reached the Host.

That was not a reason to weaken reauthentication. It was a reason to name its two halves. A first connection proves the signed identity and authorizes it under current policy. An equivalent reattach must prove the signed identity, recipient, freshness, replay claim and current blacklist again, then show that every bound connection field is unchanged. It must not ask onboarding/contact policy to authorize the same still-live connection twice, nor re-read admin lists and rebuild permission authority that connection already owns. It republishes the existing mode, profile, transcript, and dashboard state to the new physical browser socket. Custom verifiers retain their existing gate unless they explicitly provide the same narrow reattach half.

The tests make the boundary visible. One session-loop test puts two freshly signed, equivalent CONNECT frames into the same logical stream and expects two CONNECTED replies for the same session. Six negative cases try to change the identity, recipient, capability, session, protocol, or reuse a signature. Every one is rejected. A regression makes trust policy raise the exact missing-file error and proves reattach never calls it; another makes admin/mode initialization raise the same error and proves it is not repeated. A newly blacklisted caller is still refused. Together with the existing Host, relay, replay, profile, and command-signing coverage, 76 focused tests passed in the follow-up run.

The production screenshot was the important measurement. Before this change, the transcript proved that Codex had finished while a red authentication error claimed the page was broken. That contradiction exposed two different bugs: O Chat held on to a cleared error, and the Host created a new error during reload. Fixing only the banner would have made the first screenshot cleaner and the next refresh fail again.

Connection identity is a stack of names: physical WebSocket, relay route, authenticated caller, OIP application session, and provider session. In the simple case they begin and end together, so it is tempting to treat them as one thing. Reload is where they separate. The browser connection can be new while the relay route and application session are deliberately old. A correct reconnect policy preserves that continuity without letting any authority move.

The lesson is small enough to reuse: idempotence is not the absence of a security check. It is the security check that proves nothing changed.

Star us on GitHub

If ConnectOnion saves you time, a ⭐ goes a long way — and earns you a coffee chat with our founder.