Conversation
jefflitt1
force-pushed
the
upstream-web-tauri-guards
branch
from
August 29, 2026 02:35
480ee58 to
8e2d66b
Compare
🔐 Codex Security Review
|
jefflitt1
force-pushed
the
upstream-web-tauri-guards
branch
from
August 29, 2026 10:12
8e2d66b to
e37caa1
Compare
Several call sites into @tauri-apps/api still assume a native Tauri runtime and throw when the desktop chat UI is served as a plain web page instead (no window.__TAURI_INTERNALS__): "Cannot read properties of undefined (reading 'transformCallback'/'invoke')". Most of the codebase already guards this correctly with isTauri() before calling listen()/invoke() (e.g. audioWorklet.ts); these were the remaining gaps, found by exercising a web-served build end-to-end: - useAgentsDataRefresh, useNestNotifications, HuddleContext, HuddleBar, HuddleIndicator, MobilePairingCard: guard listen() registration with isTauri(). - usePreventSleep: guard both effects. The listen() registration throws without Tauri internals, and setPreventSleepActive is fired fire-and-forget (`void`, no .catch), so an unguarded call there is a genuine unhandled rejection rather than a logged warning. Sleep prevention is a native OS concept with no browser equivalent. - deep-link.ts: the pending-deep-link queue is populated by the Rust backend and only exists in the native shell, so both the invoke() drain and the listen()-based registrations are guarded. In listenForDeepLinks the guard precedes deps.onAddCommunityAvailable — that subscription only wakes the native queue, so in a browser it would register a callback that can never do work. - legacyCommunityStorage.ts: the legacy Sprout WebKit SQLite database read only exists in the native shell — skip it in a web runtime. deep-link.test.mjs fakes a Tauri runtime with __TAURI_INTERNALS__, but isTauri() reads globalThis.isTauri, which the native shell injects alongside the IPC internals. The fake runtime now sets it too, so those tests continue to exercise the native branch. Guards stay at the consumer rather than inside shared/api/tauri.ts: that module is an API wrapper, not a policy layer, and its callers already handle failure (useCommunityInit wraps its calls in try/catch and falls back to needsSetup, App.tsx catches is_shared_identity and falls back to false). Guarding inside the wrapper would only suppress those console warnings, which are the signal that a packaged native build is misconfigured. Signed-off-by: Jeff Litt <jglittell@gmail.com>
deep-link.test.mjs sets globalThis.isTauri = true so its fake runtime matches what isTauri() actually reads, which means every test in that file exercises the native branch. Nothing covered the guards themselves, so they could all regress with the suite still green. deep-link.web.test.mjs sets no Tauri globals at all — that is exactly the browser runtime the guards exist for, so it needs no mocking and runs against the real @tauri-apps/api. Each guarded export is asserted to resolve to a callable cleanup without routing a payload or reaching the bridge; if a guard leaks, the real invoke()/listen() reject and the await fails. It is a separate file because node's per-process ESM module cache would otherwise hand a second import of deep-link.ts the instance already evaluated under the other file's globals. node --test runs each matched file in its own process, which sidesteps that. Verified by mutation rather than by a passing run: removing any one of the five guards fails this file — listenForDeepLinks fails two of the six cases, the other four fail one each. Signed-off-by: Jeff Litt <jglittell@gmail.com>
jefflitt1
force-pushed
the
upstream-web-tauri-guards
branch
from
August 29, 2026 16:00
e37caa1 to
48baee2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@tauri-apps/apistill assume a native Tauri runtime and throw when the desktop chat UI is served as a plain web page instead (nowindow.__TAURI_INTERNALS__):"Cannot read properties of undefined (reading 'transformCallback'/'invoke')". Most of the codebase already guards this correctly withisTauri()before callinglisten()/invoke()(e.g.audioWorklet.ts); this closes the boot-reachable gaps found by exercising a web-served build end-to-end.useAgentsDataRefresh,useNestNotifications,HuddleContext,HuddleBar,HuddleIndicator,MobilePairingCard: guardlisten()registration withisTauri().usePreventSleep: guard both effects. Thelisten()registration throws without Tauri internals, andsetPreventSleepActiveis fired fire-and-forget (void, no.catch), so an unguarded call there is a genuine unhandled rejection rather than a logged warning.deep-link.ts: the pending-deep-link queue is populated by the Rust backend and only exists in the native shell, so both theinvoke()drain and thelisten()-based registrations are guarded. InlistenForDeepLinksthe guard precedesdeps.onAddCommunityAvailable— that subscription only wakes the native queue, so in a browser it would register a callback that can never do work.legacyCommunityStorage.ts: the legacy Sprout WebKit SQLite database read only exists in the native shell — skip it in a web runtime.Guards stay at the consumer rather than inside
shared/api/tauri.ts: that module is an API wrapper, not a policy layer, and its callers already handle failure (useCommunityInitwraps its calls in try/catch and falls back toneedsSetup,App.tsxcatchesis_shared_identityand falls back tofalse). Guarding inside the wrapper would only suppress those console warnings, which are the signal that a packaged native build is misconfigured. It would also grow a file the repo's own size ratchet already holds at its limit.Apart from the prevent-sleep unhandled rejection, these failures were already caught and logged as console warnings, so this doesn't change behavior for native Tauri users.
Scope note: this covers the boot-reachable native calls surfaced by a standalone-web smoke test, not an exhaustive audit of every
invoke()/listen()in the desktop client.Independent of #3189, but both were found while exercising the same web-served build, and neither on its own makes the bundle usable as a plain web page.
Test plan
pnpm build(tsc + vite build) — passespnpm test— 5799/5799 passingjust file-size-check— passespnpm check(biome + px-text + pubkey-truncation) — cleandeep-link.web.test.mjssets no Tauri globals, so it runs against the real@tauri-apps/apiin the browser runtime the guards exist for. Verified by mutation, not just a green run: removing any one of the five guards fails that file (listenForDeepLinksfails two of six cases, the rest one each).shared/api/tauri.ts; those guards were dropped deliberately (see above), so a browser load still logs theis_shared_identity command failedwarning fromApp.tsx's existing.catch. That warning is intended — it is the signal that the native bridge is absent, and suppressing it was the only thing those guards bought.Related
Checked open PRs/issues for duplicates before submitting — no duplicate found. #3027 is open in an adjacent but different subsystem (relay-side SPA routing config, vs. this PR's desktop-client Tauri guards) for a similarly-motivated self-hosted deployment; not overlapping, noted for context.