Repository navigation
fix(desktop): the fork's window is a secure context again - #176
Conversation
Since 0.0.43-ap.10 the window at t3code://app was not a secure context. crypto.subtle was undefined there, so T3 Connect linked but every relay call failed with "Could not load relay DPoP proof key." The terminal's WebAssembly, the clipboard API and passkeys were broken the same way. Electron keeps the secure, fetch and CORS schemes of the last registerSchemesAsPrivileged call only. The Clerk bridge makes its own call for the scheme it is given. Upstream gives it the renderer scheme. The fork gives it the t3code-fork link scheme, so that call dropped t3code from all three lists. DesktopClerk now lists every scheme again right after it creates the bridge. The bridge still gets the link scheme, so the official install keeps its t3code:// links.
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
|
Code review, two axes. Each finding has a confidence and an estimated severity. StandardsStandards review: PR 176Outcome: no blocking findings. One hard violation and one process breach, both in the PR body. Six judgement calls in the code, all low severity. Hard violations
Judgement calls
Compliant
Note on finding 1: this fork leaves the model and harness line out of pull requests on purpose, so no change is wanted there. SpecSpec review: PR 176Verdict: the fix matches the spec. No blocking gap. Five small findings. I read the diff, the code around it and the PR body. I could not run the test or read Checked and correct
(a) Missing or partial
(b) Scope creepNone found. (c) Implemented but possibly wrong
Worth carrying forwardThe PR reports more broken since ap.10 than T3 Connect: the built-in terminal (same-origin |
In the installed fork (0.0.43-ap.10) T3 Connect links, but every relay call then fails with "Could not load relay DPoP proof key." and "Could not load the environment authorization key."
Cause
The window at
t3code://appis not a secure context, socrypto.subtleis undefined there.Electron keeps the secure, fetch and CORS schemes of the last
protocol.registerSchemesAsPrivilegedcall only. Two calls run beforeready:registerDesktopSchemePrivilegesSyncinElectronProtocol.tslistst3code,t3code-dev,t3code-forkandt3code-fork-dev.createClerkBridgein@clerk/electron0.0.44 (dist/esm/index.jsline 293) then listsrenderer.schemealone.Upstream passes the renderer scheme to the bridge, so its second call keeps
t3codesecure. Since #172 the fork passes thet3code-forklink scheme, so the second call dropst3codefrom the secure, fetch and CORS lists. The standard list adds up across calls, which is why the page still loads and still has localStorage.The comment in
ElectronProtocol.tssaid Electron allows this call once. It allows more than one. The last one wins for those three lists.Evidence
Built app.
main(8cd0f93) was built withnode scripts/build-desktop-artifact.ts --platform win --target nsis --keep-stageand run unpacked on an empty temp home. A DevTools protocol client read the main window:That
TypeErroris the one at the end of the exception chain in the user's trace.Which call wins. The same Electron binary (44.4.2) ran a 40 line script with no app code and no Clerk. Call 1 lists
t3codeandt3code-forkwith all five privileges. Call 2 listst3code-forkonly. It then loads a blank page at each origin in a hidden window:The one change that should flip it, a third call listing both schemes again:
What changed
DesktopClerk.ts:createDesktopClerkBridgecallsregisterDesktopSchemePrivilegesSync()right aftercreateClerkBridgereturns, in the same synchronous step. The full list is the last call, so all four schemes keep every privilege.ElectronProtocol.ts: exportsregisterDesktopSchemePrivilegesSyncand replaces the "allows this call once" comment with what Electron does.DesktopClerk.schemePrivileges.test.ts(new): see below.DesktopClerk.test.ts: mockselectron, becauseDesktopClerknow calls intoprotocol.fork-features.json:fork-connect-renderer-originnow describes the second call, its keep line names it, and its test list has the new test.The bridge still gets the
t3code-forklink scheme. No origin or authentication check changed. The fork still does not claimt3code://from the OS.Before and after, in the built app
Both builds ran unpacked on an empty temp home.
t3code://appmainwindow.isSecureContextfalsetruetypeof crypto.subtle"undefined""object"crypto.subtle.generateKey(ECDSA P-256)TypeErrorok: ECDSA private, extractable falsetypeof crypto.randomUUID"undefined""function"typeof navigator.clipboard"undefined""object"typeof navigator.credentials,PublicKeyCredential"undefined""object","function"typeof navigator.serviceWorker,navigator.locks,navigator.storage,caches"undefined""object"typeof navigator.mediaDevices"undefined""object"fetch("t3code://app/index.html")TypeError: Failed to fetchok 200fetch("/assets/ghostty-vt-DdA0Zryv.wasm")TypeError: Failed to fetchfetch ok 200, 630932 bytesindexedDB.open,localStorageClerk.statusreadyreadyWhat else was broken since ap.10
All of these come from the same lost privileges, and all are fixed by this change:
apps/web/src/cloud/dpop.ts):crypto.subtlewas undefined. This is the reported bug.apps/web/src/terminal/ghostty/runtime.tsloads its WebAssembly withfetch, andfetchto the window's own origin failed.navigator.clipboardwas undefined.useCopyToClipboardfalls back toexecCommandfor plain text, but its paste path (readText) and the directnavigator.clipboardcalls do not: copy path inChatView.tsx,ChatMarkdown.tsx,PreviewView.tsx, copy image inMediaActions.tsx, and terminal copy and paste inghostty/surface.ts.navigator.credentialsandPublicKeyCredentialwere undefined in the page.apps/web/src/browser/browserRecording.ts):navigator.mediaDeviceswas undefined.Not broken: IndexedDB and localStorage worked, because the scheme stayed standard. The app does not call
crypto.randomUUID, service workers,navigator.locksorcachesdirectly. Its ownrandomUUIDusescrypto.getRandomValues, which works without a secure context.The test, and how it fails on
mainDesktopClerk.schemePrivileges.test.tsruns the real@clerk/electroncreateClerkBridgebehindDesktopClerk.layer, afterElectronProtocol.layerSchemePrivileges, the ordermain.tsuses.electronis replaced by a stand-in that keeps what the experiment above measured: standard and stream schemes add up, and the last call sets the secure, fetch and CORS lists. It asserts that the renderer scheme and the link scheme are in all three lists, for production and development, and that the bridge still claims only the link scheme from the OS.A unit test cannot prove a secure context, so the built-app runs above are the proof of that. The test pins the registration order that causes it.
With
DesktopClerk.tsandElectronProtocol.tsput back tomainand the test kept:With the fix:
Sign-in and the storage copy, in the built app
Sign-in, on the empty home, signed out. The run clicked Settings, Connections, then "Sign in to T3 Connect", and stopped at the email field:
Storage copy. A bare Electron script wrote four made-up values at
t3code-fork://appinto a second temp home. The branch build then started on that home twice:All four values read back at
t3code://appon both launches, and the marker file holdscopied.Registry.
HKCU\Software\Classes\t3code-forkandHKCU\Software\Classes\t3codewere exported before every launch and restored after. During each runt3code-forkpointed at the unpacked build andt3codestill pointed at the official install.Checks
The failures are the same on
mainon this Windows machine. With every change put back tomain:The 22 desktop failures are Linux-only tests: the Niri, KDE and Hyprland screenshot helpers and a Unix file-permission launcher test.
One note on the local build. On this machine
build-desktop-artifact.tsexits 1 at its last step on bothmainand this branch, after the app is packaged:Refusing to report success: C:\Users\Glyn\node_modules is visible from the probe directory. The unpacked app in the kept stage is complete, and that is what ran. The build usedT3CODE_DESKTOP_REUSE_RESOURCE_MONITOR=truewith a prebuilt resource monitor binary, because the Visual Studio Spectre libraries are missing here.