Skip to content

feat: Add blacklist mode for browser host filtering - #1164

Closed
sharunkumar wants to merge 10 commits into
manaflow-ai:mainfrom
sharunkumar:feature/browser-host-blacklist-mode
Closed

sharunkumar wants to merge 10 commits into
manaflow-ai:mainfrom
sharunkumar:feature/browser-host-blacklist-mode

Conversation

@sharunkumar

@sharunkumar sharunkumar commented Mar 10, 2026 •

Copy link
Copy Markdown

Summary

Adds a Whitelist/Blacklist mode toggle to the browser host list setting, so users can specify domains that should be blocked from opening in the embedded browser rather than only being able to whitelist allowed domains.

Motivation

Based on #124

Currently the only way to control which sites open in the cmux browser is a whitelist — you must list every host you want to open internally. For users who want most sites in cmux but need to block a few specific domains (e.g. sites that don't render well, SSO login pages, or sites with download flows), maintaining an exhaustive whitelist is impractical. A blacklist mode inverts the logic: list only the hosts you want to exclude, and everything else opens in cmux.

Behavior

Mode Empty list Non-empty list
Whitelist (default) All hosts open in cmux Only listed hosts open in cmux
Blacklist All hosts open in cmux Listed hosts open externally, everything else in cmux

Screenshots

image image

Bug fix: sidebar PR links now respect host filtering

Sidebar PR link clicks (both individual clicks and batch "Open All Workspace PR Links") previously bypassed host whitelist/blacklist filtering entirely. They only checked the openSidebarPullRequestLinksInCmuxBrowser toggle without consulting shouldOpenExternally() or shouldOpenInCmuxBrowser(host:). This meant blacklisted domains would still open in the embedded browser when clicked from the sidebar, even though terminal link clicks correctly routed them externally. Both openPullRequestLink() and openWorkspacePullRequestsInConfiguredBrowser() now apply the same host filtering checks as the terminal link flow.

Summary by CodeRabbit

  • New Features
    • Added host list mode setting to choose between whitelist and blacklist for browser host filtering.
    • Added host blacklist configuration with support for wildcards to control which hosts open in the default browser versus the embedded browser.

Add a Whitelist/Blacklist mode toggle to the browser host list setting,
allowing users to specify domains that should be blocked from opening in
the embedded browser instead of only being able to whitelist allowed
domains.

Data model (BrowserPanel.swift):
- Add HostListMode enum (whitelist/blacklist) and browserHostListModeKey
  UserDefaults key (default: "whitelist" for backward compatibility)
- Add shouldOpenInCmuxBrowser(host:) that respects the configured mode:
  whitelist mode matches existing behavior, blacklist mode inverts the
  check so listed hosts open externally and everything else opens in cmux
- Keep hostMatchesWhitelist() intact for backward compatibility
- Extract hostMatchesEntry() private helper to share pattern-matching
  logic between both code paths

URL routing (GhosttyTerminalView.swift):
- Replace hostMatchesWhitelist() call with shouldOpenInCmuxBrowser() at
  the terminal link click decision point

Settings UI (cmuxApp.swift):
- Add @AppStorage binding for browserHostListMode
- Add segmented Picker control (Whitelist/Blacklist) in browser settings
- Make host list label and subtitle text dynamic based on selected mode
- Include new setting in the reset-to-defaults flow

Shell open wrapper (Resources/bin/open):
- Read browserHostListMode from UserDefaults
- Add should_open_in_cmux() function that inverts the whitelist check
  when in blacklist mode
- Update URL routing loop to use should_open_in_cmux() instead of
  host_matches_whitelist() directly

Localization (Localizable.xcstrings):
- Add English strings for hostListMode, hostBlacklist labels/subtitles
  and Whitelist/Blacklist picker options
- Add Japanese translations (needs_review)

Tests:
- Add BrowserHostListModeTests (10 cases) covering default mode, mode
  reading, invalid fallback, whitelist-mode parity, and blacklist-mode
  behavior (empty list, exact match, miss, wildcard, case-insensitive,
  dot-boundary)
- Add 5 Python open wrapper tests for blacklist mode (match, miss,
  wildcard, empty list, explicit whitelist mode)
@vercel

vercel Bot commented Mar 10, 2026

Copy link
Copy Markdown

@sharunkumar is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 10, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request extends the browser host filtering feature by introducing support for both whitelist and blacklist modes. It adds localization strings, UI controls for mode selection, refactored decision logic across Swift and shell components, and test coverage for the new functionality.

Changes

Cohort / File(s) Summary
Localization & Settings Infrastructure
Resources/Localizable.xcstrings, Sources/Panels/BrowserPanel.swift, Sources/cmuxApp.swift
Added localization entries for host blacklist/whitelist mode settings; introduced HostListMode enum with whitelist/blacklist cases; added UI segmented picker for mode selection in settings; implemented hostListMode(defaults:) accessor and mode-aware opening decision method shouldOpenInCmuxBrowser(host:defaults:).
Browser Decision Logic
Sources/GhosttyTerminalView.swift, Sources/ContentView.swift
Updated host filtering predicates from hostMatchesWhitelist to shouldOpenInCmuxBrowser to incorporate mode-aware decisions; applied conditional external URL routing based on host list rules in PR link opening flows.
Shell Script
Resources/bin/open
Added host_list_mode variable with whitelist default; generalized matching logic via host_matches_list_pattern function; implemented should_open_in_cmux(url) to evaluate URLs against host patterns in the active mode; updated settings reading to support both mode and patterns.
Test Coverage
tests/test_open_wrapper.py
Extended test wrapper function with host_list_mode parameter; added environment propagation for mode-aware testing; introduced test cases for blacklist/whitelist scenarios and edge cases.

Sequence Diagram

sequenceDiagram
    participant User
    participant Settings as Settings UI
    participant Logic as Decision Logic
    participant cmuxBrowser as cmux Browser
    participant External as External Browser

    User->>Settings: Configure host list mode<br/>(whitelist/blacklist)
    Settings->>Settings: Store mode & patterns
    
    User->>Logic: Click/open URL
    activate Logic
    Logic->>Logic: Read host list mode
    Logic->>Logic: Extract host from URL
    Logic->>Logic: Match host against patterns
    
    alt Whitelist Mode
        Logic->>Logic: Host in patterns?
        alt Yes
            Logic->>cmuxBrowser: Open in cmux
        else No
            Logic->>External: Open externally
        end
    else Blacklist Mode
        Logic->>Logic: Host in patterns?
        alt Yes
            Logic->>External: Open externally
        else No
            Logic->>cmuxBrowser: Open in cmux
        end
    end
    deactivate Logic
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • PR #2660: Modifies BrowserPanel.swift navigation decision predicates; shares code-level interaction with the new shouldOpenInCmuxBrowser logic being introduced.
  • PR #912: Updates URL-open flow in GhosttyTerminalView.swift and BrowserPanel.swift; related through shared browser link handling refactoring.

Poem

🐰 A filter grows two heads instead of one,
Whitelist whispers, blacklist has its fun,
Swift decides which path the URL takes—
Browser or external? The host list makes!
Hop to settings, click the segmented view,
Mode-aware matching—everything new! 🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main feature: adding blacklist mode to browser host filtering, which is the core intent of the PR.
Description check ✅ Passed The PR description fully covers the template requirements: Summary (what and why), screenshots of behavior, and bug fix explanation. However, Testing section and Demo Video are missing, and the checklist is incomplete.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@sharunkumar sharunkumar changed the title Add blacklist mode for browser host filtering feat: Add blacklist mode for browser host filtering Mar 10, 2026
@sharunkumar
sharunkumar marked this pull request as ready for review March 11, 2026 17:52

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 7 files


Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

@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.

Actionable comments posted: 1

🧹 Nitpick comments (4)
tests/test_open_wrapper.py (3)

843-871: Consider auto-discovering test_* functions.

These new cases only run because they were manually appended here. That makes the harness easy to break silently when someone adds another test and forgets to register it in main().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_open_wrapper.py` around lines 843 - 871, Replace the manual
sequence of calls in main() with an auto-discovery loop: inspect the module
globals for callables whose names start with "test_", sort the names for
deterministic order, and invoke each function with the failures list (so
functions like test_toggle_disabled_passthrough and others are discovered
automatically); ensure main() still returns the appropriate exit code after
running all discovered tests and keep the failures list parameter name
unchanged.

34-45: Rename the helper arg now that it is not whitelist-only.

run_wrapper(..., whitelist=...) is now used by blacklist tests too, which makes those call sites misleading. A neutral name like host_list or host_entries would fit both modes and make future cases easier to read.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_open_wrapper.py` around lines 34 - 45, The helper parameter name
whitelist in the run_wrapper function should be renamed to a neutral name (e.g.,
host_list or host_entries) because it’s used by both whitelist and blacklist
tests; update the run_wrapper signature (def run_wrapper(..., host_list: str |
None, ...)) and rename all usages within the function body and every test call
site that passes whitelist= to use host_list= (or host_entries=) so call sites
for blacklist tests become semantically correct and consistent with the new
parameter name.

733-840: Add one blacklist IDN regression case.

The new blacklist path only covers ASCII hosts. Since this wrapper already has Unicode/punycode edge cases in whitelist mode, one equivalent blacklist case would help catch a mode-specific regression where host normalization works in whitelist mode but not after the blacklist inversion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_open_wrapper.py` around lines 733 - 840, Add a regression test for
IDN handling in blacklist mode by adding a new test (e.g.
test_blacklist_mode_idn_regression) that calls run_wrapper with a
Unicode/punycode host URL and host_list_mode="blacklist" and a whitelist
containing the corresponding punycode entry; assert with expect that the wrapper
returns code 0 and that cmux_log is empty and open_log contains the URL
(mirroring other blacklist tests like
test_blacklist_mode_match_opens_externally), so host normalization for IDNs is
exercised under the blacklist inversion.
Sources/GhosttyTerminalView.swift (1)

2152-2156: Use neutral logging for the shared host-list decision.

shouldOpenInCmuxBrowser(host:) now covers both whitelist misses and blacklist matches, so hostList miss is inaccurate in blacklist mode and will mislead debug traces. Prefer a neutral reason like hostList blocked here.

Suggested tweak
-                    dlog("link.openURL target=embedded but hostList miss host=\(host) url=\(url)")
+                    dlog("link.openURL target=embedded but hostList blocked host=\(host) url=\(url)")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 2152 - 2156, Update the DEBUG
logging in the branch where
BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host:) returns false to use a
neutral message that covers both whitelist misses and blacklist matches; replace
the existing dlog call that says "hostList miss host=\(host) url=\(url)" with a
neutral phrase such as "hostList blocked host=\(host) url=\(url)" (or "hostList
decision=blocked") so the log accurately reflects
BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host:) outcomes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/cmuxApp.swift`:
- Line 2859: The AppStorage-backed browserHostListMode (stored under
BrowserLinkOpenSettings.browserHostListModeKey) can contain unexpected strings,
which leaves the segmented control unselected; normalize/validate this value
before driving the UI by mapping the raw String to the known set of modes (using
the enum or known constants from BrowserLinkOpenSettings) and falling back to
BrowserLinkOpenSettings.defaultBrowserHostListMode when the rawValue is invalid;
implement this either via a computed property that converts browserHostListMode
to a validated enum/normalized String for UI use, or add a small normalization
step where browserHostListMode is read/initialized so the UI always receives a
guaranteed-valid mode (reference symbols: browserHostListMode,
BrowserLinkOpenSettings.browserHostListModeKey,
BrowserLinkOpenSettings.defaultBrowserHostListMode).

---

Nitpick comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 2152-2156: Update the DEBUG logging in the branch where
BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host:) returns false to use a
neutral message that covers both whitelist misses and blacklist matches; replace
the existing dlog call that says "hostList miss host=\(host) url=\(url)" with a
neutral phrase such as "hostList blocked host=\(host) url=\(url)" (or "hostList
decision=blocked") so the log accurately reflects
BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host:) outcomes.

In `@tests/test_open_wrapper.py`:
- Around line 843-871: Replace the manual sequence of calls in main() with an
auto-discovery loop: inspect the module globals for callables whose names start
with "test_", sort the names for deterministic order, and invoke each function
with the failures list (so functions like test_toggle_disabled_passthrough and
others are discovered automatically); ensure main() still returns the
appropriate exit code after running all discovered tests and keep the failures
list parameter name unchanged.
- Around line 34-45: The helper parameter name whitelist in the run_wrapper
function should be renamed to a neutral name (e.g., host_list or host_entries)
because it’s used by both whitelist and blacklist tests; update the run_wrapper
signature (def run_wrapper(..., host_list: str | None, ...)) and rename all
usages within the function body and every test call site that passes whitelist=
to use host_list= (or host_entries=) so call sites for blacklist tests become
semantically correct and consistent with the new parameter name.
- Around line 733-840: Add a regression test for IDN handling in blacklist mode
by adding a new test (e.g. test_blacklist_mode_idn_regression) that calls
run_wrapper with a Unicode/punycode host URL and host_list_mode="blacklist" and
a whitelist containing the corresponding punycode entry; assert with expect that
the wrapper returns code 0 and that cmux_log is empty and open_log contains the
URL (mirroring other blacklist tests like
test_blacklist_mode_match_opens_externally), so host normalization for IDNs is
exercised under the blacklist inversion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e731f41-a873-4e16-8acb-50e83bda68e7

📥 Commits

Reviewing files that changed from the base of the PR and between 00587ed and 5be9892.

📒 Files selected for processing (7)
  • Resources/Localizable.xcstrings
  • Resources/bin/open
  • Sources/GhosttyTerminalView.swift
  • Sources/Panels/BrowserPanel.swift
  • Sources/cmuxApp.swift
  • cmuxTests/CmuxWebViewKeyEquivalentTests.swift
  • tests/test_open_wrapper.py

Comment thread Sources/cmuxApp.swift
@greptile-apps

greptile-apps Bot commented Mar 11, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a whitelist/blacklist mode toggle to the browser host-list setting, letting users specify which domains to block from the embedded browser rather than only being able to allowlist. The change is implemented consistently across both the Swift app layer and the Bash open wrapper, and is well-covered by unit tests and regression tests.

Key changes:

  • BrowserLinkOpenSettings gains a HostListMode enum, a hostListMode(defaults:) reader, and a new shouldOpenInCmuxBrowser(host:defaults:) function that wraps the existing pattern-matching logic and inverts the result in blacklist mode.
  • GhosttyTerminalView call-site updated from hostMatchesWhitelist (mode-unaware) to shouldOpenInCmuxBrowser (mode-aware) — this is the single production routing gate.
  • The Bash open wrapper adds a should_open_in_cmux function that reads browserHostListMode from defaults and delegates to the existing host_matches_whitelist helper, inverting for blacklist mode. The same browserHostWhitelist UserDefaults key is reused for storage in both modes (the UI label updates dynamically).
  • Settings UI in cmuxApp.swift shows a segmented Whitelist/Blacklist picker and dynamically re-labels the host list TextEditor; reset-to-defaults is also included.
  • Test coverage is thorough: 9 new XCTest cases and 5 new Python regression tests cover empty-list, match, miss, wildcard, case-insensitivity, and dot-boundary edge cases.

Minor issues worth noting:

  • hostMatchesWhitelist remains a public, mode-unaware helper; it's now superseded by shouldOpenInCmuxBrowser for routing but is still referenced by pre-existing tests. Marking it private or adding a clear "do not use for routing" note would prevent future misuse.
  • Variable names whitelist_raw / whitelist_patterns in the Bash script are semantically misleading when operating in blacklist mode. Cosmetic only.
  • Blacklisted URLs in the Bash wrapper share the failed_urls array with genuinely failed cmux opens; a brief inline comment distinguishing the two cases would improve auditability.

Confidence Score: 4/5

  • Safe to merge — core blacklist logic is correct and well-tested in both Swift and Bash; no functional bugs found.
  • The implementation is consistent across the two independent code paths (Swift + Bash wrapper), the single production call-site in GhosttyTerminalView.swift was correctly updated, the settings UI and reset handler are complete, and test coverage is thorough. The only issues are a style/footgun concern around the still-public hostMatchesWhitelist function and cosmetic variable naming in the Bash script — neither affects correctness.
  • Sources/Panels/BrowserPanel.swift — the public hostMatchesWhitelist function could lead to future routing bugs if called directly instead of shouldOpenInCmuxBrowser.

Important Files Changed

Filename Overview
Sources/Panels/BrowserPanel.swift Adds HostListMode enum, hostListMode(defaults:) reader, and the new shouldOpenInCmuxBrowser(host:defaults:) function that wraps the existing hostMatchesWhitelist logic and inverts the result for blacklist mode. The old hostMatchesWhitelist function remains public and mode-unaware, but production code no longer calls it directly.
Resources/bin/open Adds host_list_mode variable (default "whitelist"), reads browserHostListMode from defaults, and adds should_open_in_cmux() that delegates to the existing host_matches_whitelist() helper and inverts the result in blacklist mode. Logic is correct; variable naming (whitelist_raw, whitelist_patterns) is slightly misleading in blacklist context but non-functional.
Sources/GhosttyTerminalView.swift Single call-site change: replaces BrowserLinkOpenSettings.hostMatchesWhitelist(host) with BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: host) so terminal link clicks respect the new blacklist mode. Change is minimal and correct.
Sources/cmuxApp.swift Adds @AppStorage binding for browserHostListMode, renders a segmented Picker for whitelist/blacklist selection, dynamically switches the host-list TextEditor's title/subtitle based on the selected mode, and includes the new setting in the reset-to-defaults handler.
cmuxTests/CmuxWebViewKeyEquivalentTests.swift Adds BrowserHostListModeTests covering default mode detection, explicit whitelist/blacklist reads, empty-list pass-through, match/miss for both modes, wildcard blocking, case-insensitivity, and dot-boundary edge cases. Coverage is thorough.
tests/test_open_wrapper.py Adds five new Python regression tests for the bash open wrapper covering blacklist match, miss, wildcard, empty list, and explicit whitelist mode. Tests use the existing fake-defaults harness and correctly set FAKE_DEFAULTS_HOST_LIST_MODE.
Resources/Localizable.xcstrings Adds four new localisation keys (settings.browser.hostBlacklist, settings.browser.hostBlacklist.subtitle, settings.browser.hostListMode, and .whitelist/.blacklist variants) with English and Japanese translations. Strings are accurate and consistent with the UI copy.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["URL clicked / open https://... intercepted"] --> B{openTerminalLinks\nor interceptOpen\nenabled?}
    B -- No --> EXT1["system_open / NSWorkspace.open (external)"]
    B -- Yes --> C{Host list\nempty?}
    C -- Yes / Empty --> CMX1["Open in cmux browser"]
    C -- No --> D{hostListMode}
    D -- whitelist --> E{Host matches\nlist entry?}
    E -- Yes --> CMX2["Open in cmux browser"]
    E -- No --> EXT2["Open externally"]
    D -- blacklist --> F{Host matches\nlist entry?}
    F -- Yes --> EXT3["Open externally\n(blocked host)"]
    F -- No --> CMX3["Open in cmux browser"]

    style CMX1 fill:#4caf50,color:#fff
    style CMX2 fill:#4caf50,color:#fff
    style CMX3 fill:#4caf50,color:#fff
    style EXT1 fill:#f44336,color:#fff
    style EXT2 fill:#f44336,color:#fff
    style EXT3 fill:#f44336,color:#fff
Loading

Comments Outside Diff (1)

  1. Resources/bin/open, line 480-491 (link)

    Blacklisted URLs are silently lumped into failed_urls

    URLs that are intentionally blocked by the blacklist are appended to failed_urls and then passed to system_open at the end, which is the correct behavior. However, the comment on line 489 ("Fall back to system open for unmatched args and URLs that failed") doesn't mention this third category, which can make the code harder to audit. Consider renaming the variable or adding a comment when the URL is added due to blacklist filtering:

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Last reviewed commit: 5be9892

Comment thread Sources/Panels/BrowserPanel.swift Outdated
Comment thread Resources/bin/open

@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 (2)
Resources/bin/open (1)

342-356: Redundant empty-check is harmless but could be simplified.

The host_list_patterns empty check at line 344-346 is now redundant since should_open_in_cmux() handles the empty case at lines 362-364 before calling this function. The check is defensive and harmless, but if this function is only called from should_open_in_cmux, it could be simplified.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/bin/open` around lines 342 - 356, Remove the redundant empty-array
guard inside host_matches_list_pattern(): delete the if block that returns 0
when host_list_patterns is empty, since should_open_in_cmux() already handles
the empty-case before calling this function; keep the normalize_host and
host_matches_pattern logic intact (leave host="$(normalize_host "$url")" and the
for loop over host_list_patterns), and ensure callers like should_open_in_cmux()
still perform the empty check.
Sources/Panels/BrowserPanel.swift (1)

280-294: Consider deduplicating hostMatchesWhitelist and hostMatchesEntry.

Both functions share nearly identical pattern-matching logic:

  • Normalize the host
  • Iterate through raw patterns, normalize each, and check for match

The key difference is that hostMatchesWhitelist returns true for empty patterns while hostMatchesEntry does not. Since hostMatchesWhitelist is now private, consider whether it's still needed or if shouldOpenInCmuxBrowser (which handles the empty-patterns case explicitly) makes it obsolete.

Also applies to: 314-324

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Panels/BrowserPanel.swift` around lines 280 - 294, The host-matching
logic is duplicated between hostMatchesWhitelist(_:, defaults:) and
hostMatchesEntry(_:, defaults:); extract the shared behavior into a single
helper (e.g., hostMatchesPatterns(normalizedHost: String, rawPatterns: [String],
allowEmpty: Bool) or a private function hostMatches(normalizedHost: String?,
rawPatterns: [String], acceptEmpty: Bool)) that performs normalization of host
via BrowserInsecureHTTPSettings.normalizeHost, normalizes each pattern via
normalizeWhitelistPattern, and checks matches with hostMatchesPattern; then have
hostMatchesWhitelist call the helper with acceptEmpty = true (or keep a thin
wrapper that returns true for empty list) and have hostMatchesEntry call it with
acceptEmpty = false (or directly use the helper), updating all callers
(including shouldOpenInCmuxBrowser if relevant) to use the unified helper and
remove the duplicated loop logic from hostMatchesWhitelist and hostMatchesEntry.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Resources/bin/open`:
- Around line 342-356: Remove the redundant empty-array guard inside
host_matches_list_pattern(): delete the if block that returns 0 when
host_list_patterns is empty, since should_open_in_cmux() already handles the
empty-case before calling this function; keep the normalize_host and
host_matches_pattern logic intact (leave host="$(normalize_host "$url")" and the
for loop over host_list_patterns), and ensure callers like should_open_in_cmux()
still perform the empty check.

In `@Sources/Panels/BrowserPanel.swift`:
- Around line 280-294: The host-matching logic is duplicated between
hostMatchesWhitelist(_:, defaults:) and hostMatchesEntry(_:, defaults:); extract
the shared behavior into a single helper (e.g.,
hostMatchesPatterns(normalizedHost: String, rawPatterns: [String], allowEmpty:
Bool) or a private function hostMatches(normalizedHost: String?, rawPatterns:
[String], acceptEmpty: Bool)) that performs normalization of host via
BrowserInsecureHTTPSettings.normalizeHost, normalizes each pattern via
normalizeWhitelistPattern, and checks matches with hostMatchesPattern; then have
hostMatchesWhitelist call the helper with acceptEmpty = true (or keep a thin
wrapper that returns true for empty list) and have hostMatchesEntry call it with
acceptEmpty = false (or directly use the helper), updating all callers
(including shouldOpenInCmuxBrowser if relevant) to use the unified helper and
remove the duplicated loop logic from hostMatchesWhitelist and hostMatchesEntry.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4f8fe91-6030-4bd8-a6ea-1912959d44b6

📥 Commits

Reviewing files that changed from the base of the PR and between 5be9892 and dc177db.

📒 Files selected for processing (3)
  • Resources/bin/open
  • Sources/Panels/BrowserPanel.swift
  • Sources/cmuxApp.swift

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/Panels/BrowserPanel.swift">

<violation number="1" location="Sources/Panels/BrowserPanel.swift:283">
P3: `hostMatchesWhitelist` is unused and duplicates `hostMatchesEntry`; keeping both helpers increases maintenance drift risk. Remove the unused function or consolidate to a single matcher.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/Panels/BrowserPanel.swift 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)
cmuxTests/CmuxWebViewKeyEquivalentTests.swift (1)

12527-12563: Avoid repeating the persisted "blacklist" literal in every behavior test.

testBlacklistModeIsReadFromDefaults() already pins the storage value. For the rest of this suite, deriving the stored mode from the enum/raw-value source of truth would reduce drift if the persisted spelling ever changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/CmuxWebViewKeyEquivalentTests.swift` around lines 12527 - 12563,
Replace the repeated literal "blacklist" in tests with the canonical
enum/raw-value from BrowserLinkOpenSettings so tests derive the persisted mode
from the source-of-truth; for example, call
defaults.set(BrowserLinkOpenSettings.BrowserHostListMode.blacklist.rawValue,
forKey: BrowserLinkOpenSettings.browserHostListModeKey) (or the equivalent enum
name available on BrowserLinkOpenSettings) in each test that currently uses
"blacklist" so the stored value follows the enum/rawValue instead of a
hard-coded string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cmuxTests/CmuxWebViewKeyEquivalentTests.swift`:
- Around line 12527-12563: Replace the repeated literal "blacklist" in tests
with the canonical enum/raw-value from BrowserLinkOpenSettings so tests derive
the persisted mode from the source-of-truth; for example, call
defaults.set(BrowserLinkOpenSettings.BrowserHostListMode.blacklist.rawValue,
forKey: BrowserLinkOpenSettings.browserHostListModeKey) (or the equivalent enum
name available on BrowserLinkOpenSettings) in each test that currently uses
"blacklist" so the stored value follows the enum/rawValue instead of a
hard-coded string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81653aed-e6a7-45b4-b73b-1c12ba31b92e

📥 Commits

Reviewing files that changed from the base of the PR and between dc177db and ccaace2.

📒 Files selected for processing (2)
  • Sources/Panels/BrowserPanel.swift
  • cmuxTests/CmuxWebViewKeyEquivalentTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/Panels/BrowserPanel.swift

Sidebar PR link clicks (both individual and batch 'Open All Workspace PR
Links') bypassed the host filtering entirely — they only checked the
openSidebarPullRequestLinksInCmuxBrowser boolean toggle without consulting
BrowserLinkOpenSettings.shouldOpenExternally() or
BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host:).

This meant blacklisted domains (e.g. github.tesla.com) would still open
in the embedded browser when clicked from the sidebar, even though
terminal link clicks correctly routed them externally.

Both openPullRequestLink() and openWorkspacePullRequestsInConfiguredBrowser()
now apply the same external-open-pattern and host whitelist/blacklist checks
that GhosttyTerminalView uses before routing to the embedded browser.

@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)
Sources/ContentView.swift (1)

5885-5891: Centralize the sidebar PR routing predicate.

These two paths now reimplement the same “external patterns + host list mode” decision inline. It works today, but it makes sidebar PR routing easy to desync again the next time BrowserLinkOpenSettings / browser-panel routing changes. Please move this into one shared helper and call it from both the batch and single-link flows.

♻️ Suggested consolidation
+    private func shouldOpenSidebarPullRequestExternally(_ url: URL) -> Bool {
+        if BrowserLinkOpenSettings.shouldOpenExternally(url) {
+            return true
+        }
+
+        guard let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? "") else {
+            return false
+        }
+
+        return !BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: host)
+    }
+
     private func openWorkspacePullRequestsInConfiguredBrowser() -> Bool {
         guard let workspace = tabManager.selectedWorkspace else { return false }
         let pullRequests = workspace.sidebarPullRequestsInDisplayOrder()
         guard !pullRequests.isEmpty else { return false }

         var openedCount = 0
         if openSidebarPullRequestLinksInCmuxBrowser {
             for pullRequest in pullRequests {
                 let url = pullRequest.url
-                // Respect external-open patterns and host whitelist/blacklist settings.
-                let shouldOpenExternally = BrowserLinkOpenSettings.shouldOpenExternally(url)
-                    || {
-                        guard let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? "") else { return false }
-                        return !BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: host)
-                    }()
-                if shouldOpenExternally {
+                if shouldOpenSidebarPullRequestExternally(url) {
                     if NSWorkspace.shared.open(url) { openedCount += 1 }
                 } else if tabManager.openBrowser(url: url, insertAtEnd: true) != nil {
                     openedCount += 1
                 } else if NSWorkspace.shared.open(url) {
                     openedCount += 1
@@
     private func openPullRequestLink(_ url: URL) {
         updateSelection()
         if openSidebarPullRequestLinksInCmuxBrowser {
-            // Respect external-open patterns and host whitelist/blacklist settings.
-            if BrowserLinkOpenSettings.shouldOpenExternally(url) {
-                NSWorkspace.shared.open(url)
-                return
-            }
-            if let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? ""),
-               !BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: host) {
+            if shouldOpenSidebarPullRequestExternally(url) {
                 NSWorkspace.shared.open(url)
                 return
             }
             if tabManager.openBrowser(
                 inWorkspace: tab.id,

Also applies to: 10259-10268

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 5885 - 5891, The inline predicate
that decides "open externally vs in CMux browser" is duplicated; extract it into
a single shared helper (e.g., add a method on BrowserLinkOpenSettings like
shouldOpenExternallyForSidebar(url: URL) or a static helper function) that
encapsulates the current logic:
BrowserLinkOpenSettings.shouldOpenExternally(url) OR (normalize host via
BrowserInsecureHTTPSettings.normalizeHost(...) and then
!BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host:)). Replace the duplicated
inline block (the local shouldOpenExternally variable used with pullRequest.url
in both the single-link flow and the batch flow) to call the new helper so both
paths use the same centralized predicate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Sources/ContentView.swift`:
- Around line 5885-5891: The inline predicate that decides "open externally vs
in CMux browser" is duplicated; extract it into a single shared helper (e.g.,
add a method on BrowserLinkOpenSettings like shouldOpenExternallyForSidebar(url:
URL) or a static helper function) that encapsulates the current logic:
BrowserLinkOpenSettings.shouldOpenExternally(url) OR (normalize host via
BrowserInsecureHTTPSettings.normalizeHost(...) and then
!BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host:)). Replace the duplicated
inline block (the local shouldOpenExternally variable used with pullRequest.url
in both the single-link flow and the batch flow) to call the new helper so both
paths use the same centralized predicate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9cabe3f5-879f-499e-8352-e44013cf4083

📥 Commits

Reviewing files that changed from the base of the PR and between ccaace2 and b8a8c6e.

📒 Files selected for processing (1)
  • Sources/ContentView.swift

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/ContentView.swift">

<violation number="1" location="Sources/ContentView.swift:5889">
P2: Normalization failure is treated as "open in cmux" here, which can bypass host-list filtering for non-normalizable hosts.</violation>

<violation number="2" location="Sources/ContentView.swift:10264">
P2: This `if let` bypasses host-list enforcement when host normalization fails, allowing embedded open for hosts that should be treated as non-eligible.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/ContentView.swift
Comment on lines +10264 to +10265
if let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? ""),
!BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: host) {

@cubic-dev-ai cubic-dev-ai Bot Mar 11, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This if let bypasses host-list enforcement when host normalization fails, allowing embedded open for hosts that should be treated as non-eligible.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/ContentView.swift, line 10264:

<comment>This `if let` bypasses host-list enforcement when host normalization fails, allowing embedded open for hosts that should be treated as non-eligible.</comment>

<file context>
@@ -10247,6 +10256,16 @@ private struct TabItemView: View {
+                NSWorkspace.shared.open(url)
+                return
+            }
+            if let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? ""),
+               !BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: host) {
+                NSWorkspace.shared.open(url)
</file context>
Suggested change
if let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? ""),
!BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: host) {
if !BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: url.host ?? "") {
Fix with Cubic

Comment thread Sources/ContentView.swift
Comment on lines +5889 to +5890
guard let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? "") else { return false }
return !BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: host)

@cubic-dev-ai cubic-dev-ai Bot Mar 11, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Normalization failure is treated as "open in cmux" here, which can bypass host-list filtering for non-normalizable hosts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/ContentView.swift, line 5889:

<comment>Normalization failure is treated as "open in cmux" here, which can bypass host-list filtering for non-normalizable hosts.</comment>

<file context>
@@ -5882,9 +5882,18 @@ struct ContentView: View {
+                // Respect external-open patterns and host whitelist/blacklist settings.
+                let shouldOpenExternally = BrowserLinkOpenSettings.shouldOpenExternally(url)
+                    || {
+                        guard let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? "") else { return false }
+                        return !BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: host)
+                    }()
</file context>
Suggested change
guard let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? "") else { return false }
return !BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: host)
return !BrowserLinkOpenSettings.shouldOpenInCmuxBrowser(host: url.host ?? "")
Fix with Cubic

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.

2 participants