Skip to content

fix: harden Ctrl+click URL handling (activation, allow-list, hover preview)#86

Open
manuacl wants to merge 1 commit into
am-will:mainfrom
manuacl:fix/issue-85-ctrl-click-external-browser
Open

fix: harden Ctrl+click URL handling (activation, allow-list, hover preview)#86
manuacl wants to merge 1 commit into
am-will:mainfrom
manuacl:fix/issue-85-ctrl-click-external-browser

Conversation

@manuacl

@manuacl manuacl commented May 23, 2026

Copy link
Copy Markdown

Summary

Three layered changes to the OSC 8 / Ctrl+click URL hand-off:

  1. Activation token (fix Ctrl+click on terminal URLs doesn't raise the browser window on Wayland #85). Pass an AppLaunchContext from the default GdkDisplay when calling gio::AppInfo::launch_default_for_uri, so Wayland gets a valid xdg-activation token and the browser window can actually raise itself (Konsole works this way via KIO; we didn't). Fall back to spawning xdg-open (with a detached reaper thread, no zombie children) when GIO returns Err, for AppImage / sandboxed environments where the bundled GIO can't dispatch. Preserve the GIO error in the fallback log so future silent-launch failures stay diagnosable.

  2. Strict URI scheme allow-list (security hardening). Validate the scheme against http / https / mailto only, case-insensitive per RFC 3986 §3.1. The issue Ctrl+click on terminal URLs doesn't raise the browser window on Wayland #85 description has the full rationale; short version is that anything beyond web + email opens us up to single-click RCE via file://, gvfs auto-mount (ftp/ftps/smb/nfs/dav/sftp), or app-specific URI handlers with a history of CVEs.

  3. OSC 8 hover URL preview. Limux wasn't handling GHOSTTY_ACTION_MOUSE_OVER_LINK at all, so users couldn't tell where a labelled hyperlink actually pointed before clicking — classic OSC 8 link-masking vector. Now a libadwaita gtk::Popover (parented to the GLArea, identical look to the right-click context menu by construction — same helpers) shows the target URL above the cursor while Ctrl is held, following the cursor inside the link region and auto-flipping below if there's no room above.

Closes #85.

Test plan

  • ./scripts/check.sh — fmt, clippy -D warnings, full workspace tests pass (189 in limux-host-linux).
  • Unit tests cover scheme allow-list:
    • Navigable schemes (http/https/mailto)
    • Mixed-case schemes (HTTPS://, Https://, MAILTO:, …)
    • Explicit rejection of javascript:, data:, vbscript:, file:, ftp:/ftps:, smb:/nfs:/dav:/davs:/sftp:, ssh:, magnet:, chrome:, about:, vscode:, slack:, leading whitespace, bare paths, malformed inputs.
  • Manual test on Bazzite / Plasma 6 Wayland (debug build):
    • Ctrl+click https://example.com → opens Firefox and raises the window (was: window stayed hidden).
    • Ctrl+click OSC 8 hyperlink with uppercase scheme (HTTPS://example.com) → works (was: silently ignored).
    • ps -ef | grep defunct clean after many Ctrl+clicks (reaper thread).
    • Ctrl+hover over an OSC 8 labelled link (\e]8;;https://example.com\e\\Click here\e]8;;\e\\) → libadwaita popover with the target URL appears above the cursor, tracks the cursor inside the link region, hides on leave.

Commits

  • 3641b72 fix: raise browser window on Ctrl+click via xdg-activation token
  • 0f241df fix: tighten Ctrl+click URL allow-list to http/https/mailto
  • 0e169a7 feat: show OSC 8 URL preview on Ctrl+hover

(Can be squashed on merge.)

@manuacl manuacl changed the title fix: raise browser window on Ctrl+click via xdg-activation token fix: harden Ctrl+click URL handling (activation, allow-list, hover preview) May 23, 2026
manuacl added a commit to manuacl/limux that referenced this pull request May 24, 2026
@manuacl manuacl force-pushed the fix/issue-85-ctrl-click-external-browser branch from 0e169a7 to 2bd1dd7 Compare May 25, 2026 14:23
@manuacl manuacl force-pushed the fix/issue-85-ctrl-click-external-browser branch from 67c9cb6 to 3ad3c51 Compare May 25, 2026 14:55
…review

Three related fixes around Ctrl+click on terminal URLs.

1. Raise the browser window via xdg-activation (am-will#85)

   open_url_in_external_browser previously called
   gio::AppInfo::launch_default_for_uri(url, None). Passing no
   AppLaunchContext means GIO emits no xdg-activation token, so under
   Wayland focus-stealing prevention the target browser opens the URL
   but cannot raise its window. Konsole works because KIO wires the
   token in its own launch path.

   - Pass Some(&display.app_launch_context()) so GIO emits the token.
   - Fall back to spawning xdg-open when GIO returns Err (AppImage /
     sandboxed contexts), with a detached reaper thread to avoid
     zombie children.
   - Preserve the GIO error message in the fallback log so a future
     silent-launch failure stays diagnosable.

2. Tighten the URL allow-list to http/https/mailto

   Threat-model the Ctrl+click hand-off as hostile terminal output:
   an OSC 8 hyperlink crafted by a remote shell, a `cat` of a
   malicious file, or a colleague's repro that includes a link.
   Clicking such a link must not lead to code execution.

   The previous list included ftp/ftps/file, which all delegate to
   gvfs / the local file handler:
   - file:// runs .desktop, scripts, and binaries via the registered
     handler — direct RCE on a hostile link.
   - ftp://, ftps:// auto-mount via gvfs and let the mounted share
     execute binaries (positive.security/blog/url-open-rce).

   Drop them. Keep only http/https/mailto — the minimal set
   recommended by the Positive Security writeup. Document the
   rationale in code so future additions need a proper threat-model
   review. Test matrix now explicitly rejects file:, ftp:, ftps:,
   smb:, nfs:, dav:, davs:, sftp:, vbscript:, vscode:, slack:.

3. Show OSC 8 URL preview on Ctrl+hover

   Ghostty already detects OSC 8 hyperlinks but the C embedding API
   leaves it to the host to surface the target URL. Limux wasn't
   handling GHOSTTY_ACTION_MOUSE_OVER_LINK at all, so users had no
   way to tell where a labelled link actually pointed before clicking
   — the well-known OSC 8 link-masking vector.

   - Add the GHOSTTY_ACTION_MOUSE_OVER_LINK tag and its {url, len}
     payload to the FFI bindings.
   - Surface the URL through a libadwaita gtk::Popover parented to
     the GLArea, so the look matches the right-click context menu by
     construction (same widget, same styling helpers). Two small
     helpers — build_floating_popover and build_popover_inner_box —
     are extracted and reused by both the hover preview and the
     existing context menu to keep them in sync.
   - Track the cursor position on every motion event and reposition
     the popover while it's visible; Ghostty only emits
     MOUSE_OVER_LINK on enter/leave, so without manual repositioning
     the popover would freeze at its initial anchor.
   - Anchor the popover above the cursor with a 14 px vertical gap
     big enough to clear an accessibility cursor skin. GTK auto-flips
     below the cursor when there isn't room above.
   - Drop the unused .limux-link-preview CSS rules (the popover is
     styled by the shared build_popover_inner_box() helper, not by a
     dedicated class).

Closes am-will#85

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@manuacl manuacl force-pushed the fix/issue-85-ctrl-click-external-browser branch from 3ad3c51 to f3d02a2 Compare May 26, 2026 08:04
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.

Ctrl+click on terminal URLs doesn't raise the browser window on Wayland

1 participant