Skip to content

cmux ssh: keep resume bindings, notification replies and local status off the relay - #15145

Open
austinywang wants to merge 4 commits into
mainfrom
remote-relay-narrowing
Open

austinywang wants to merge 4 commits into
mainfrom
remote-relay-narrowing

Conversation

@austinywang

@austinywang austinywang commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The cmux ssh CLI relay forwarded more than any remote flow needs. After this change, a process on the remote host can no longer:

  • Read, write or clear Mac-side resume bindings. The relay dropped surface.resume.set bindings and redacted resume_binding from surface.list. Still, surface.resume.set/get/clear were in the relay schema, and set carried the only exemption from the command-parameter deny. A relayed get returned the full binding, command included, and a relayed clear deleted it. All three are out of the schema now, and command-bearing params (initial_command, command, tmux_start_command, pane_start_command) are denied on every method with no exceptions. ControlSurfaceResumeTarget.registeredBinding still returns nil for relay callers, as a second layer.
  • Request a reply through a notification. notification.create_for_target loses reply_shape from the relay schema. The coordinator also ignores a relayed reply_shape. The app delivers the notification with the .sshRelay(ownerWorkspaceID:) origin, which clamps click, agent context, sound override and hooks, and it prefixes the title with the remote destination (for example dev-box: Build finished) so a remote can't pass its notification off as a local one.
  • Read local connection details. For relay callers, workspace.remote.status and the terminal_session_launching/connected/end replies now carry only enabled, state and connected in remote. The destination, proxy, local ports and daemon details stay on the Mac. Nothing on the remote side reads the fuller payload. Local callers get the full payload, same as before.

The remote CLI (daemon/remote) never sends reply_shape or surface.resume.*. On the Mac, cmux notify adds reply_shape only with --reply. So normal remote notify, status and session flows are unchanged.

Relay authorization analysis

  • Local command or content execution: reduced. This PR adds no methods. It removes the only relay method that could carry a command (surface.resume.set) and the notification reply path, which could have typed input into a local pane.
  • Unowned objects: workspace and surface ID scoping is unchanged. Three fewer methods can name a surface.
  • Local-state exposure: reduced. Remote status replies no longer include local ports, proxy or daemon details. A relay can't read resume bindings.

Squatting relay listener

We also looked at whether a process on the remote host could bind the relay port before cmux ssh does and impersonate the Mac. This PR changes nothing here:

  • Token and relay ID: the client never transmits the token. Before it answers, it needs the relay ID from a 0600 file or the environment.
  • Replay and timing: the MAC is bound to the server nonce. Relay metadata is published only after the forward binds.
  • Credentials: they stay out of argv.

A squatter running as the same uid could already read the token file. A proof from the server would need a protocol change that deployed remotes wouldn't enforce.

Overlap with open PRs

#14929 and #14915 also edit RemoteRelayRoutingSchema, RemoteRelayAuthorizationPolicy and daemon/remote/README.md, so whichever lands second will need a textual merge. They don't conflict in behavior:

Testing

The failing tests were committed first, in cee4bef. The fix is in 459905c.

Command Before the fix (cee4bef) After the fix (459905c)
swift test --package-path Packages/macOS/CmuxRemoteWorkspace --filter RemoteRelayNarrowing 4 tests failed with 27 issues 4 tests passed
swift test --package-path Packages/macOS/CmuxControlSocket --filter RemoteRelayNarrowing 4 tests failed with 5 issues 4 tests passed

On 459905c, these broader package runs also passed:

  • swift test --package-path Packages/macOS/CmuxRemoteWorkspace: 160 tests in 26 suites.
  • swift test --package-path Packages/macOS/CmuxControlSocket --filter "Remote|Notification|Resume|Workspace": 117 tests in 14 suites.

python3 scripts/verify-local.py --affected origin/main --swift-changed origin/main passed all 15 selected checks: swift-syntax, xcstrings, localization, the project, test-wiring and package-group checks, launch-policy, remote-tmux-waits and feature-flags.

Not run locally, because the build disk was nearly full: the app build, cmuxTests and a tagged-build dogfood. That covers the app-side changes in TerminalController+ControlNotificationContext.swift and TerminalNotificationLiveRetargetDelivery.swift. It also covers the updated RemoteResumeBindingTests Kiro case, which now expects remote_relay_method_denied. CI covers the app build and app tests. Until those pass, the app-side changes are unverified.

Localization audit:

  • notification.remoteRelay.hostFallback ("Remote host") has all 9 macOS locales.
  • notification.remoteRelay.title (%1$@: %2$@) has a format omission record in scripts/localization-allowed-omissions.json.
  • python3 scripts/localization_catalog.py check reported 0 parity errors.
  • ./scripts/localize-changes also reported 0 parity errors. It raised one human-attention flag on ControlCommandCoordinator+Notification.swift. That's a false positive from an existing doc comment that mentions String(localized:); the file has no new user-facing strings.

Changelog

Changed: Remote SSH sessions can no longer read or change Mac-side resume commands, request replies to their notifications, or read local connection details through the cmux CLI relay; their notifications now show the remote host in the title

Checklist

  • Behavior changes have added or updated tests
  • Localization audited; result stated above
  • Relay authorization questions answered above (no methods added; three removed)
  • Reviewed with a subagent before merge

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Keeps resume bindings, notification reply fields, and local connection details off the cmux ssh relay, so a remote host can no longer read or change Mac-side resume commands, request a reply through a notification, or read local ports, proxy, and daemon details.

  • Removes surface.resume.set/get/clear from the relay schema; command-bearing params are now denied on every method with no exceptions.
  • Relay calls to notification.create_for_target ignore reply_shape; the app delivers such notifications with the relay origin, no reply affordance, and the remote destination in the title.
  • For relay callers, workspace.remote.status and the terminal_session_* lifecycle replies carry only enabled, state, and connected; local callers keep the full payload.

The new relay-narrowing tests pass after the fix; the recent merges from main only reconciled Localizable.xcstrings. #14929 and #14915 also edit the relay schema and policy files, so whichever lands second will need a textual merge.

Written for commit 7f7004b. Summary will update on new commits.

Review in cubic

austinywang and others added 2 commits September 27, 2026 21:02
… and remote status details

Remote relay callers can still reach surface.resume.set/get/clear, request a
notification reply field, and read the full remote status payload. These
tests fail until the relay schema and coordinator narrow those paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… relay

The relay no longer forwards surface.resume.get/set/clear, so a remote
session cannot read, write or clear a Mac-side resume command, and
command-bearing params are denied on every method with no exceptions.

A relayed notification is delivered with the relay origin, no reply
affordance and the remote destination in its title; reply_shape is out of
the relay schema.

workspace.remote.status and the terminal_session_* lifecycle replies carry
only enabled, state and connected in remote for a relay caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 1 minute.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: eb32457d-5207-45a2-a428-cab4369afd52

📥 Commits

Reviewing files that changed from the base of the PR and between f7ee3bc and 7f7004b.

📒 Files selected for processing (17)
  • CLAUDE.md
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Notification/ControlCommandCoordinator+Notification.swift
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Workspace/ControlCommandCoordinator+Workspace.swift
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Workspace/ControlCommandCoordinator+WorkspaceRemoteLifecycle.swift
  • Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandCoordinatorRemoteRelayNarrowingTests.swift
  • Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/Relay/RemoteRelayAuthorizationPolicy.swift
  • Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/Relay/RemoteRelayCommandPolicy.swift
  • Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/Relay/RemoteRelayRoutingSchema.swift
  • Packages/macOS/CmuxRemoteWorkspace/Tests/CmuxRemoteWorkspaceTests/RemoteRelayNarrowingPolicyTests.swift
  • Resources/Localizable.xcstrings
  • Sources/TerminalController+ControlNotificationContext.swift
  • Sources/TerminalNotificationLiveRetargetDelivery.swift
  • cmuxTests/RemoteResumeBindingTests.swift
  • daemon/remote/README.md
  • docs/remote-daemon-spec.md
  • scripts/localization-allowed-omissions.json
  • skills/cmux-socket-policy/references/remote-relay-authorization.md

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.

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

Catch-up merge by scripts/ci/catch_up_pr.py (RFC #14631).
Merged by scripts/merge-main.sh: origin/main at ce5cb45.

Resolved conflicts:
- Resources/Localizable.xcstrings: xcstrings key-level union

Catch-up-previous-head: 459905c
Catch-up-base: ce5cb45
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

CI failure attribution

CI passes on 7f7004be34 (run 36491729003 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's.

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Dogfood tours of 7f7004be

browser-notifications-tour at 7f7004be: not run

skipped: CI built this head on a runner pool whose products the UI test Macs cannot load, and media never compiles one; gh workflow run pr-media.yml -f pr=&lt;n&gt; -f allow_compile=true does

Tours are picked by the paths globs in dogfood/scenarios/*.json; a Dogfood-tours: a, b line in the description picks them instead (none turns this off). Look at every frame before merging: a green tour only means no step failed.

Catch-up merge by scripts/ci/catch_up_pr.py (RFC #14631).
Merged by scripts/merge-main.sh: origin/main at f7ee3bc.

Resolved conflicts:
- Resources/Localizable.xcstrings: xcstrings key-level union

Catch-up-previous-head: f206129
Catch-up-base: f7ee3bc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
austinywang added a commit that referenced this pull request Sep 28, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	Resources/Localizable.xcstrings

This branch has not been deployed

No deployments
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