Skip to content

fix(navigation): open external URL schemes in the system handler - #251

Closed
zorahrel wants to merge 1 commit into
the-ora:mainfrom
zorahrel:fix/external-url-schemes
Closed

zorahrel wants to merge 1 commit into
the-ora:mainfrom
zorahrel:fix/external-url-schemes

Conversation

@zorahrel

Copy link
Copy Markdown
Contributor

Problem

Clicking a link with a non-web scheme does nothing. discord://, zoommtg://, mailto:, tel: are all silently dropped: WebKit cannot load them, and TabBrowserPageDelegate returns .allow for everything that is not a cmd-click, so the navigation dies with no feedback.

Real-world impact: opening a Discord invite from a web page never launches the app (this is how I hit it, from the invite link in this project's own README), Zoom meeting links do nothing, and mailto: links never reach the mail client.

Change

decidePolicyFor now inspects the scheme first. Anything outside the set WebKit handles (http, https, about, data, blob, file, javascript) is passed to NSWorkspace.shared.open(_:) and the navigation is cancelled, which is the standard behavior for a macOS browser.

Testing

Built Release on macOS 26.2.

  • discord:// invite link: before nothing happened, after Discord.app opens.
  • Verified scheme registration with NSWorkspace.urlForApplication(toOpen:): discord:// resolves to Discord.app, mailto: to the default mail client.
  • Regular http(s) navigation and cmd-click open-in-new-tab are unaffected, since the new branch only triggers for schemes outside the web set.

Links using a non-web scheme (discord://, zoommtg://, mailto:, tel:, ...)
do nothing when clicked. WebKit cannot load those schemes and silently
drops the navigation, so the link appears dead.

This affects every 'open in app' flow: joining a Discord server,
starting a Zoom meeting, opening a mail client from a mailto: link.

Non-web schemes are now handed to NSWorkspace, which launches the
registered application, and the navigation is cancelled.
@greptile-apps

greptile-apps Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

The PR is not safe to merge until external application launches are limited to explicit user-initiated navigation.

Findings

  1. P1 Security Unprompted external app launches ▶

Summary

This PR adds system-handler support for navigation schemes that WebKit does not handle directly.

  • Defines a fixed set of schemes that remain inside WebKit.
  • Sends other URLs to NSWorkspace and cancels their WebKit navigation.
  • The handoff currently applies to navigation actions without requiring user activation.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[WebKit navigation action] --> B{Scheme in webSchemes?}
    B -- Yes --> C{Command-click?}
    C -- Yes --> D[Open new browser tab]
    C -- No --> E[Allow WebKit navigation]
    B -- No --> F[Open URL with NSWorkspace]
    F --> G[Cancel WebKit navigation]
Loading

Reviews (1) · Last reviewed commit: "fix(navigation): open external URL schem..."

Comment on lines +19 to +25
if let url = navigationAction.request.url,
let scheme = url.scheme?.lowercased(),
!Self.webSchemes.contains(scheme)
{
// WebKit silently drops unknown schemes, so the link looks dead.
// Hand it to the system, which opens the registered app.
NSWorkspace.shared.open(url)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Unprompted external app launches

A page can initiate a custom-scheme navigation through a script, redirect, or subframe. Every WebKit policy action reaches this delegate with only its request and modifier flags, so this branch cannot verify a user gesture or main-frame link before passing the URL to NSWorkspace.open. Untrusted content can therefore launch a registered application and send it attacker-controlled URL parameters without user interaction. Restrict this handoff to explicit user-activated navigation and retain the WebKit action metadata needed to enforce that restriction.

How this was verified: Every WebKit policy action is reduced to its request and modifier flags before this unconditional non-web-scheme branch launches the registered handler.

@zorahrel zorahrel closed this Sep 22, 2026
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.

1 participant