Repository navigation
fix(web): guard secure-context-only crypto in browser client - #5471
Conversation
The web client is often served over plain HTTP on a LAN, which is not a secure context. crypto.randomUUID() and crypto.subtle are undefined there (crypto.getRandomValues is not), so unguarded calls throw "crypto.randomUUID is not a function" — passing in Electron but crashing in the browser. Route the three unguarded renderer call sites through the existing non-secure-context-safe helpers: - worktree-creation-flow.ts, launch-agent-background-session.ts -> createBrowserUuid() - orca-hook-trust.ts -> keep crypto.subtle where present (so existing trust hashes stay valid on Electron/HTTPS) and fall back to a JS hash (already-bundled tweetnacl) off a secure context. Adds a reproduction test that recreates the non-secure-context global shape and drives the real call sites; it fails on the pre-fix code with "Cannot read properties of undefined (reading 'digest')". Co-authored-by: Orca <help@stably.ai>
|
Ready to review this PR? Stage has broken it down into 4 individual chapters for you:
Chapters generated by Stage for commit 5358d9b on Jun 16, 2026 5:30am UTC. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR adds non-secure browser context (plain HTTP) support for cryptographic operations by introducing a self-contained SHA-256 fallback implementation and migrating crypto-dependent code to use fallback-aware alternatives. The 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/renderer/src/lib/orca-hook-trust.test.ts (1)
21-30: ⚡ Quick winTest doesn't verify hash compatibility between secure and non-secure contexts.
The test confirms the fallback path works in isolation, but doesn't verify that the SHA-512 fallback hash differs from the SHA-256 crypto.subtle hash for identical input. Given that trust persistence relies on hash stability (see
ensure-hooks-confirmed.tscontext snippet), consider adding an assertion that documents this mismatch:it('fallback produces different hashes than crypto.subtle (SHA-512 vs SHA-256)', async () => { const contentSHA256 = await hashOrcaHookScript('echo hi') stubCrypto(undefined) const contentSHA512 = await hashOrcaHookScript('echo hi') expect(contentSHA256).not.toBe(contentSHA512) expect(contentSHA512.length).toBeGreaterThan(contentSHA256.length) // SHA-512 is 64 bytes })This would surface the cross-context trust re-prompting behavior during code review.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 3e590e52-d139-4d97-9869-68f5bf91577b
📒 Files selected for processing (5)
src/renderer/src/lib/launch-agent-background-session.tssrc/renderer/src/lib/non-secure-context-crypto.repro.test.tssrc/renderer/src/lib/orca-hook-trust.test.tssrc/renderer/src/lib/orca-hook-trust.tssrc/renderer/src/lib/worktree-creation-flow.ts
CodeRabbit flagged that the non-secure-context fallback used nacl.hash (SHA-512) while the crypto.subtle path uses SHA-256. trustedOrcaHooks is a shared per-repo store, so a hook approved on Electron/HTTPS (SHA-256) would mismatch when re-hashed in the HTTP web client (SHA-512), forcing a spurious re-approval. Add a self-contained JS SHA-256 (sha256.ts) and use it for the fallback so both paths yield byte-identical digests. Tests cover NIST known-answer vectors, a cross-check against crypto.subtle across block boundaries, and cross-context hash equality. Co-authored-by: Orca <help@stably.ai>
…tests PR #8804 replaced two unguarded crypto.randomUUID() calls with createBrowserUuid(), matching the convention established in #5471, but shipped without extending that PR's regression-test pattern to the new call sites. Add hook-level tests for useMarkupPointerHandlers.onPointerDown and useMarkupEditor.commitPendingText under a simulated non-secure-context crypto global. Co-authored-by: Orca <help@stably.ai>
* fix: address pr-bug-scan validated finding from #6335 Replaced unguarded crypto.randomUUID() with the established createBrowserUuid() fallback in both markup shape-id call sites. * fix(review): cover markup crypto fallback call sites with regression tests PR #8804 replaced two unguarded crypto.randomUUID() calls with createBrowserUuid(), matching the convention established in #5471, but shipped without extending that PR's regression-test pattern to the new call sites. Add hook-level tests for useMarkupPointerHandlers.onPointerDown and useMarkupEditor.commitPendingText under a simulated non-secure-context crypto global. Co-authored-by: Orca <help@stably.ai> * fix(review): consolidate annotation bridge token onto createBrowserUuid BrowserPane had its own inline crypto.randomUUID guard with a Date.now()+Math.random() fallback, duplicating the getRandomValues-based fallback already centralized in createBrowserUuid() and used at every other call site in the renderer. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai> Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai>
…i#5471) Co-authored-by: Orca <help@stably.ai>
* fix: address pr-bug-scan validated finding from stablyai#6335 Replaced unguarded crypto.randomUUID() with the established createBrowserUuid() fallback in both markup shape-id call sites. * fix(review): cover markup crypto fallback call sites with regression tests PR stablyai#8804 replaced two unguarded crypto.randomUUID() calls with createBrowserUuid(), matching the convention established in stablyai#5471, but shipped without extending that PR's regression-test pattern to the new call sites. Add hook-level tests for useMarkupPointerHandlers.onPointerDown and useMarkupEditor.commitPendingText under a simulated non-secure-context crypto global. Co-authored-by: Orca <help@stably.ai> * fix(review): consolidate annotation bridge token onto createBrowserUuid BrowserPane had its own inline crypto.randomUUID guard with a Date.now()+Math.random() fallback, duplicating the getRandomValues-based fallback already centralized in createBrowserUuid() and used at every other call site in the renderer. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai> Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai>
Problem
A user reported the web client (Orca in the browser) throwing
crypto.randomUUID is not a functionand failing to work reliably.The web client is frequently served over plain HTTP on a LAN, which is not a secure context. In that case the browser leaves
crypto.randomUUID()andcrypto.subtleundefined(onlycrypto.getRandomValuesis available). Electron always runs in a secure context, so unguardedcrypto.*calls pass locally and in CI but crash in the browser — which is why this slipped through (the default vitest env isnode, wherecrypto.randomUUIDexists globally).Reproduction
Added
non-secure-context-crypto.repro.test.ts, which recreates the exact non-secure-context global shape (getRandomValuespresent,randomUUID/subtleundefined) and drives the real call sites. Against the pre-fix code it fails with:With the fix, it passes.
Fix
Route the three unguarded renderer call sites through non-secure-context-safe code:
worktree-creation-flow.tscrypto.randomUUID()createBrowserUuid()launch-agent-background-session.tsglobalThis.crypto.randomUUID()createBrowserUuid()orca-hook-trust.tscrypto.subtle.digest(...)crypto.subtle; fall back to a JS hash (tweetnacl, already bundled)The hook-trust change keeps
crypto.subtleas the primary path so existing stored trust hashes stay valid on Electron/HTTPS, and only falls back off a secure context.createBrowserUuid()/mintStablePaneId()already existed and were the intended pattern — these sites just weren't using them.Scope / what this does NOT cover
The reproduced crashes are on action paths (creating a worktree, launching an agent, hook-trust during launch). The reporter also mentioned "sessions are empty and one project is missing" on load. I traced the passive session/repo hydration path (web RPC ids use a counter, not crypto; tab hydration already uses
createBrowserUuid) and did not find a crypto cause there. That symptom may be a cascade from a thrown exception during launch, or a separate bug. Recommend asking the reporter to retest loading after this lands; if sessions are still empty, I'll open a separate investigation into the web hydration path.Testing
non-secure-context-crypto.repro.test.ts) — 3 passing, demonstrably fails on pre-fix codeorca-hook-trust.test.ts— covers both the secure path and the non-secure fallbackensure-hooks-confirmed.test.ts— 16 passingtypecheck:webandoxlintcleanMade with Orca 🐋