Skip to content

Migrate Hyper Core simulator to first-party credentials - #7

Merged
merryman merged 7 commits into
mainfrom
robin/migrate-hyper-core-simulator-to-first-party-jwt
Sep 8, 2026
Merged

merryman merged 7 commits into
mainfrom
robin/migrate-hyper-core-simulator-to-first-party-jwt

Conversation

@merryman

Copy link
Copy Markdown
Contributor

Summary

  • replace the Hyper Core cookie manager and stash with a first-party credential manager and the exact client credential envelope
  • seed credentials before initial navigation for local Chromium and AWS Device Farm, restricted to the matching origin
  • delegate Cloudflare guest authentication to the worker instead of sending renewal credentials across that boundary
  • remove the obsolete cookie CLI/config path and update simulator documentation

Depends on hypervideo/cloudflare-browser-simulator#5. Merge and deploy that worker change before releasing this simulator change.

Verification

  • cargo fmt
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo clippy -p client-simulator-browser --lib --all-features -- -D warnings
  • cargo nextest run --workspace --no-tests warn --no-fail-fast (178 tests)
  • worker formatting, type-checking, linting, and 53 tests

@merryman
merryman requested review from Ehlster and rksm September 8, 2026 06:29
@merryman
merryman force-pushed the robin/migrate-hyper-core-simulator-to-first-party-jwt branch from cb880fc to ada03d9 Compare September 8, 2026 08:44
Comment thread browser/src/participant/mod.rs Outdated
let base_url = session_url.origin().unicode_serialization();
let cookie = cookie_manager.give_cookie(&base_url);
let name = cookie.as_ref().map(BorrowedCookie::username);
let credentials = credentials_manager.give_credentials(&session_url);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Skip Core credential borrowing for local Hyper Lite participants

When Core credentials are cached for the same origin, this path gives a Lite participant the cached username. FrontendAuth::for_kind then drops the credentials immediately because Lite does not use them. The next spawn can borrow the same entry and get the same name. ParticipantStore::add uses that name as its map key, so it replaces the earlier participant and drops its task handle.

I reproduced two local /m/ spawns leaving only one participant in the store. Only borrow credentials when the frontend is Hyper Core, as the Device Farm path already does. The remote stub path also borrows a name and immediately returns the credentials, so it should skip borrowing too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, good catch — fixed in f74e5cb. Local participants now only borrow credentials for Core, and the remote stub does not borrow them at all. Added a regression test for two Lite/stub spawns with cached Core credentials on the same origin; both participants now stay in the store.

Comment thread browser/src/auth.rs Outdated
fn drop(&mut self) {
self.manager.return_cookie(self.domain.clone(), self.cookie.clone());
self.manager
.return_credentials(self.server_url.clone(), self.credentials.clone());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Retain credentials renewed in the browser before returning the identity

The new expiry checks fix repeated reuse of an expired cache entry, but this still returns the original envelope. Hyper Core writes renewed credentials to browser storage, and no path copies them back into BorrowedCredentials or the disk stash.

If a browser runs past the original renewal token's 24-hour lifetime, it can hold valid replacement credentials while return_credentials discards its original pair. The next run creates a new guest even though the previous identity was still renewable. Restarting the simulator also loses the replacement because the stash only records the initial fetch.

Read the current browser envelope before closing the session, then update the pool and persisted entry. In the original browser probe, storage held renewed-final before shutdown, but the next checkout returned issued-access.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, the expiry check alone was only half the fix. f74e5cb now reads the browser envelope before teardown and copies the renewed pair back into the borrowed credentials and disk stash. This covers local Chromium and Device Farm, including cleanup after a failed join. Added regression coverage for reuse and persistence after the original pair expires; a failed read/save logs a warning without preventing shutdown. All 185 workspace tests pass.

let key = serde_json::to_string(STORAGE_KEY).context("failed to encode credential storage key")?;
let credentials = serde_json::to_string(credentials).context("failed to encode first-party credentials")?;
Ok(format!(
"if (globalThis.location.origin === {realm} && globalThis.localStorage.getItem({key}) === null) {{ globalThis.localStorage.setItem({key}, {credentials}); }}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Install credentials before the first local room navigation

The new empty-storage guard can prevent the intended identity from ever reaching local Chromium. LocalChromiumSession::start_inner still calls create_page_retry before building authentication, and create_page opens the room URL. Hyper Core's lobby requests a permit during startup, so that first document can create and store its own guest. When join_session finally installs this script and navigates again, the key already exists and the script skips the borrowed credentials. Cleanup then saves that other guest back into the pool and stash.

I reproduced this with the real local driver and a page that models the lobby's initial credential write. The room never observed the simulator-issued token. The separate renewal and Lite-name probes now pass.

Open a blank page first, install the credentials, and only then navigate to the room. Keep the empty-storage guard for later navigations so SDK renewals survive.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, you were right — we missed that first navigation. Fixed in 164613c: both new and reused tabs now open about:blank, then the frontend installs the credentials before navigating to the room. Removed the HTTP-response wait for the blank page and kept the empty-storage guard. Added a real-Chromium regression that reproduced the page-created guest before the fix; it now checks the intended identity on first load, preserves renewals on later navigation, and returns the renewed credentials to the pool. All 186 workspace tests pass.

@rksm rksm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@merryman
merryman merged commit ff83b04 into main Sep 8, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants