Skip to content

fix(desktop): reliable dev grida:// deep-link routing — unique dev bundle id + per-env grida-dev:// scheme (GRIDA-SEC-005)#956

Merged
softmarshmallow merged 3 commits into
mainfrom
fix/desktop-dev-deeplink-bundle-id
Jul 7, 2026
Merged

fix(desktop): reliable dev grida:// deep-link routing — unique dev bundle id + per-env grida-dev:// scheme (GRIDA-SEC-005)#956
softmarshmallow merged 3 commits into
mainfrom
fix/desktop-dev-deeplink-bundle-id

Conversation

@softmarshmallow

Copy link
Copy Markdown
Member

Makes desktop sign-in's deep-link handoff route reliably to the dev app on macOS and coexist with an installed production Grida — the GRIDA-SEC-005 PKCE return path. Full context/history: #955.

Problem

Desktop sign-in returns through a grida://auth/callback deep link. In dev on macOS this was unreliable in three compounding ways:

  1. Wrong app. Every un-rebranded dev Electron shares the generic com.github.Electron bundle id, and macOS LaunchServices resolves a URL scheme by bundle id — so the link could open another project's Electron.
  2. No app. app.setAsDefaultProtocolClient is documented to take effect only for packaged apps on macOS, so an unpackaged dev build never registered a handler at all ("There is no application set to open the URL grida://").
  3. Collision with prod. Once fixed, dev still collided with an installed production Grida — both declare grida://, so LaunchServices picks a default handler nondeterministically (the /Applications app tends to win).

Fix (two layers)

1. Unique dev identity + declarative registrationscripts/prepare-dev-electron-branding.mjs (already runs before every electron-forge start) now stamps a globally-unique CFBundleIdentifier on the dev Electron (co.grida.desktop.dev / co.grida.insiders.dev), declares the scheme via CFBundleURLTypes (the registration LaunchServices actually reads — what forge bakes into packaged builds), ad-hoc re-signs so the signature id matches the patched plist, and lsregister -fs it.

2. Per-environment scheme — local dev/insiders register and return to grida-dev://; production keeps grida://, so they can never fight over one OS handler when both are installed. Single source DEEP_LINK_SCHEME in desktop/src/env.ts (mirrors EDITOR_BASE_URL); main.ts registers it; the protocol router + argv classifier accept both; forge.config bakes the channel-appropriate scheme for packaged builds; the editor's DESKTOP_AUTH_REDIRECT is env-derived; supabase local allowlists grida-dev://auth/callback.

Security — GRIDA-SEC-005

Reviewed and security-neutral:

  • The router stays stateless / fixed-target / verifier-gated for both schemes (proven by tests).
  • The redirect target is a build-time constant (NODE_ENV / app.isPackaged), never request input → stays non-attacker-controllable, the property the boundary depends on.
  • Production is entirely unchanged; dev adds grida-dev:// with identical enforcement.

SECURITY.md (enforcement step 6 + Files-bound list) and the desktop skill are aligned in this PR.

Verification

  • desktop tsc + 106 tests (incl. a new grida-dev:// routing case)
  • editor tsc + 25 GRIDA-SEC-005 tests
  • supabase reloaded → GOTRUE_URI_ALLOW_LIST carries grida-dev://auth/callback
  • confirmed the dev bundle is the sole grida-dev:// claimant, and the live sign-in round-trip lands in the dev app

Reviewer notes

  • macOS-centric (the collision is macOS LaunchServices behavior); Windows/Linux ride the same DEEP_LINK_SCHEME via setAsDefaultProtocolClient + forge MIME/protocols.
  • Packaged insiders targets the localhost editor, so it registers grida-dev too (forge protocols is channel-aware) to stay aligned with the editor's dev redirect.
  • The supabase change is local-only; the hosted dashboard keeps only grida:// (prod). grida-dev never belongs in the hosted allowlist.

Closes #955

…ndle id + per-env grida-dev:// scheme (GRIDA-SEC-005)

Desktop sign-in (the GRIDA-SEC-005 PKCE deep link) was unreliable in dev on
macOS:

- `grida://auth/callback` opened the WRONG Electron app — every un-rebranded
  dev Electron shares the generic `com.github.Electron` bundle id, and macOS
  LaunchServices routes a URL scheme by bundle id, so the link could hit
  another project's Electron (observed: a sibling repo's dev app).
- Or NO app — `app.setAsDefaultProtocolClient` is documented to take effect
  only for PACKAGED apps on macOS, so an unpackaged dev build never registered
  a handler at all ("There is no application set to open the URL grida://").
- And even once fixed, dev collided with an installed production Grida: both
  declare `grida://`, so LaunchServices picks one default handler
  nondeterministically (the /Applications app tends to win).

Fix, in two layers:

1) scripts/prepare-dev-electron-branding.mjs (already runs before every
   `electron-forge start`) now stamps a globally-unique CFBundleIdentifier on
   the dev Electron (co.grida.desktop.dev / co.grida.insiders.dev), DECLARES
   the scheme via CFBundleURLTypes (the registration LaunchServices actually
   reads — what forge bakes into packaged builds), ad-hoc re-signs so the
   signature identifier matches the patched plist, and `lsregister -f`s it.
   Isolates dev from other Electron projects and gives macOS a real
   declarative handler despite the packaged-only runtime limitation.

2) Per-environment scheme: local dev/insiders register and return to
   `grida-dev://`; production keeps `grida://`, so the two can never fight
   over one OS handler when both are installed. Single source
   DEEP_LINK_SCHEME in desktop/src/env.ts (mirrors EDITOR_BASE_URL); main.ts
   registers it; the protocol router + argv classifier accept both;
   forge.config bakes the channel-appropriate scheme for packaged builds; the
   editor's DESKTOP_AUTH_REDIRECT is env-derived; supabase local allowlists
   grida-dev://auth/callback.

GRIDA-SEC-005 reviewed, security-neutral: the router stays stateless /
fixed-target / verifier-gated for both schemes; the redirect target is a
build-time constant (NODE_ENV / app.isPackaged), never request input, so it
stays non-attacker-controllable; production is entirely unchanged. SECURITY.md
enforcement step + Files-bound list updated; the desktop skill aligned.

Verified: desktop `tsc` + 106 tests (incl. a grida-dev:// routing case);
editor `tsc` + 25 GRIDA-SEC-005 tests; supabase reloaded with the grida-dev
allowlist entry; the dev bundle is the sole grida-dev:// claimant.

Closes #955
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

6 Skipped Deployments
Project Deployment Actions Updated (UTC)
backgrounds Ignored Ignored Preview Jul 7, 2026 10:57am
blog Ignored Ignored Preview Jul 7, 2026 10:57am
code Ignored Ignored Jul 7, 2026 10:57am
docs Ignored Ignored Preview Jul 7, 2026 10:57am
grida Ignored Ignored Preview Jul 7, 2026 10:57am
viewer Ignored Ignored Preview Jul 7, 2026 10:57am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c683c22-732d-43a4-b465-57c70430954e

📥 Commits

Reviewing files that changed from the base of the PR and between 1080195 and c9b9e57.

📒 Files selected for processing (5)
  • desktop/src/deep-link.ts
  • desktop/src/env.ts
  • desktop/src/main.ts
  • desktop/src/main/open-handoff.ts
  • desktop/src/main/protocol-router.ts
✅ Files skipped from review due to trivial changes (1)
  • desktop/src/deep-link.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • desktop/src/main/protocol-router.ts
  • desktop/src/main.ts
  • desktop/src/main/open-handoff.ts
  • desktop/src/env.ts

Walkthrough

This PR adds a development-specific grida-dev:// deep-link path alongside production grida://, updating desktop registration, routing, dev branding, auth redirect constants, Supabase allowlists, and related security/docs.

Changes

Dev-specific deep-link scheme support

Layer / File(s) Summary
Scheme source and app registration
desktop/src/deep-link.ts, desktop/src/env.ts, desktop/src/main.ts
Adds the deep-link scheme type and constants, then uses them when registering the desktop app’s default protocol client.
Protocol routing and argv detection
desktop/src/main/open-handoff.ts, desktop/src/main/protocol-router.ts, desktop/src/main/protocol-router.test.ts
Accepts both owned deep-link schemes in the router and argv-based open classification, and adds coverage for the dev auth callback URL.
Packaging and dev branding
desktop/forge.config.ts, desktop/scripts/prepare-dev-electron-branding.mjs
Adds build-channel-specific scheme registration in packaging and patches the dev Electron bundle’s plist, signature, and LaunchServices registration for the dev scheme.
Auth redirect, allowlist, and docs
editor/lib/desktop/auth-deeplink.ts, supabase/config.toml, SECURITY.md, .agents/skills/desktop/SKILL.md
Makes the desktop auth redirect environment-aware, expands the Supabase redirect allowlist, and updates security and desktop skill documentation for the per-environment scheme split.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • gridaco/grida#774: Shares the same dev branding script surface in desktop/scripts/prepare-dev-electron-branding.mjs.
  • gridaco/grida#938: Also changes desktop deep-link/auth callback routing and its security boundary.
  • gridaco/grida#297: Related desktop environment-constant work in desktop/src/env.ts.

Suggested labels: desktop, security

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: reliable desktop dev deep-link routing via unique dev bundle id and per-env scheme.
Description check ✅ Passed The description clearly matches the deep-link routing fix and the macOS dev/prod conflict it addresses.
Linked Issues check ✅ Passed The PR implements the macOS bundle-id fix, dev-only grida-dev:// scheme, router/auth redirect updates, and security/docs changes required by [#955].
Out of Scope Changes check ✅ Passed All changes align with the deep-link routing fix and its supporting docs/tests; no unrelated scope is apparent.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/desktop-dev-deeplink-bundle-id

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added desktop this is a Grida Desktop App related issue security labels Jul 7, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6e0a92f70d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread desktop/src/main.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
desktop/src/env.ts (1)

18-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider centralizing all valid deep-link scheme literals.

DEEP_LINK_SCHEME gives the single active scheme per build, but protocol-router.ts ("grida:", "grida-dev:") and open-handoff.ts ("grida://", "grida-dev://") independently hardcode the full set of accepted schemes. If a scheme is ever renamed or a third variant added, three files must be updated in sync. Consider exporting a companion DEEP_LINK_SCHEMES (or similar) array from this file as the single source of truth for "all schemes this router should accept," and have the other two files import it.

♻️ Suggested consolidation
 export const DEEP_LINK_SCHEME = IS_INSIDERS || IS_DEV ? "grida-dev" : "grida";
+
+// All schemes any build's router should recognize (own scheme is chosen by
+// DEEP_LINK_SCHEME above; routing accepts both intentionally per GRIDA-SEC-005).
+export const DEEP_LINK_SCHEMES = ["grida", "grida-dev"] as const;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/env.ts` around lines 18 - 28, Centralize the accepted deep-link
scheme literals to avoid duplicating them across routing code. Add a companion
exported list in env.ts alongside DEEP_LINK_SCHEME that contains every valid
scheme variant, then update protocol-router.ts and open-handoff.ts to import and
use that shared source of truth instead of hardcoding “grida”, “grida-dev”, and
their URL forms. Keep DEEP_LINK_SCHEME as the per-build active scheme, but make
the router/hand-off acceptance set derive from the shared constant so future
renames or new variants only need one update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@desktop/src/env.ts`:
- Around line 18-28: Centralize the accepted deep-link scheme literals to avoid
duplicating them across routing code. Add a companion exported list in env.ts
alongside DEEP_LINK_SCHEME that contains every valid scheme variant, then update
protocol-router.ts and open-handoff.ts to import and use that shared source of
truth instead of hardcoding “grida”, “grida-dev”, and their URL forms. Keep
DEEP_LINK_SCHEME as the per-build active scheme, but make the router/hand-off
acceptance set derive from the shared constant so future renames or new variants
only need one update.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 05d16c1b-4978-4cc0-9202-13650b55581d

📥 Commits

Reviewing files that changed from the base of the PR and between 5a4e72f and 6e0a92f.

📒 Files selected for processing (11)
  • .agents/skills/desktop/SKILL.md
  • SECURITY.md
  • desktop/forge.config.ts
  • desktop/scripts/prepare-dev-electron-branding.mjs
  • desktop/src/env.ts
  • desktop/src/main.ts
  • desktop/src/main/open-handoff.ts
  • desktop/src/main/protocol-router.test.ts
  • desktop/src/main/protocol-router.ts
  • editor/lib/desktop/auth-deeplink.ts
  • supabase/config.toml

The repo-wide `oxfmt --check` (CI `pnpm fmt:check`) formats TOML too; the
lefthook pre-commit glob doesn't cover .toml, so the long array line slipped
through locally. No behavior change.
…eep-link schemes

Addresses PR #956 review:

- main.ts: register the scheme with the documented dev form
  `setAsDefaultProtocolClient(scheme, execPath, [appPath])` under
  `process.defaultApp`. The one-arg form recorded the bare Electron
  executable without the app entry, so on Windows dev a grida-dev:// link
  could relaunch a blank Electron instead of delivering the URL. (Codex P2)

- Centralize the accepted scheme set in a new electron-free `deep-link.ts`
  (`DEEP_LINK_SCHEMES`), consumed by env.ts (type), protocol-router.ts, and
  open-handoff.ts — one source of truth instead of three hardcodes. Kept it
  out of env.ts (which imports electron) so the electron-free argv classifier
  stays testable without a mock. (CodeRabbit nitpick)

Desktop tsc + 106 tests green; repo fmt clean.
@softmarshmallow softmarshmallow merged commit 7c51cae into main Jul 7, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

desktop this is a Grida Desktop App related issue security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop dev grida:// deep-link routing — LaunchServices bundle-id collision + dev/prod scheme conflict (GRIDA-SEC-005)

1 participant