Skip to content

cmux-tui: broker-registered iroh transport sidecar (stage 1) - #9524

Closed
azooz2003-bit wants to merge 3 commits into
mainfrom
feat-iroh-tui-transport
Closed

azooz2003-bit wants to merge 3 commits into
mainfrom
feat-iroh-tui-transport

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Aug 4, 2026 •

Copy link
Copy Markdown
Collaborator

Stage 1 of the iroh TUI transport program: a cmux-tui-iroh sidecar that makes any cmux-tui session reachable by dialing its EndpointID alone through the cmux account device registry and the managed relay fleet, including from a Docker container behind NAT with zero inbound ports.

The design doc, docs/iroh-tui-transport-stage1.md, maps each binding constraint of docs/iroh-app-transport-architecture.md to its stage-1 implementation, including the relationship to the existing cmux-remote daemon (different trust system; this sidecar is the account-scoped, arch-conformant path) and the explicitly deferred items.

listen registers the device's (user, deviceId, tag) slot with the trust broker, binds a Minimal-preset endpoint on the verified relay catalog (never n0 defaults or public DNS discovery), and admits a connection only when its first stream carries a broker-signed cmux/tui/1 pair grant naming this exact acceptor and the TLS-authenticated initiator. Admitted streams bridge byte-for-byte to the session Unix socket, so the protocol v10 JSON-lines contract crosses unchanged under the documented relay-stdio authority model. Broker state revalidates every 30 s, grant expiry closes idle sessions, and relay credentials rotate lazily (remove/insert_relay after ~30 s confirmed down, honoring mint quotas).

enroll exchanges a one-use provisioning token for a Stack session credential (headless: no interactive sign-in in a container), mints the persistent Ed25519 endpoint key + deviceId into the state root with owner-only atomic file handling, and registers the slot; restarts re-register the same slot as a heartbeat. provider implements machine-provider v1 over stdio for cmux-tui --machine-provider-command: the control process owns discovery, pair grants, and the single iroh endpoint, and per-ticket stream processes rendezvous over a private Unix socket so a second endpoint never binds the same key.

Tests: 19 unit/integration tests including a real-iroh localhost round trip (admission with a signed grant, byte-faithful bridge to a session socket, denial of a wrong-acceptor grant) and a golden test pinning the embedded relay catalog to config/iroh/managed-relay-catalog.json. scripts/iroh-docker-demo/ carries the stage-1 acceptance harness (zero-port container, EndpointID-only attach, detach/reattach, restart re-registration); the recorded demo evidence lands in this PR's conversation.

Broker counterpart (linux platform, TUI pair-grant profile, enrollment routes): #9515. This PR is independent to build but needs 9515's routes deployed to exercise the live flow.

Dictionary: relay-stdio (the existing cmux-tui transport class where a remote principal's bytes terminate at the local session Unix socket, receiving local-admin authority), pair grant (broker-signed JWS binding two same-account device endpoints to an ALPN and scope), managed relay fleet (the cmux-operated iroh relays in config/iroh/managed-relay-catalog.json), machine-provider v1 (the versioned stdio contract by which a provider hands the TUI reader/writer halves for a remote session).

🤖 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

Adds a broker-registered iroh transport sidecar (cmux-tui-iroh) that makes any cmux-tui session reachable by EndpointID through the account registry and the managed relay fleet, including behind NAT with zero inbound ports. Requires the broker routes from PR 9515 for the full flow.

  • New Features

    • New cmux-tui-iroh binary with enroll, listen, and machine-provider provider control|stream.
    • Listener: Minimal iroh endpoint on the verified relay catalog only, admits ALPN cmux/tui/1 with a broker-signed pair grant, then bridges streams to the session Unix socket.
    • Enrollment: trades a one-use token for a Stack session credential, mints a persistent Ed25519 EndpointID + deviceId, and registers the device slot (idempotent).
    • Provider: machine-provider v1 over stdio for cmux-tui --machine-provider-command, with per-ticket rendezvous over a private Unix socket.
    • State: owner-only files under <state root>/device (iroh-identity.json, iroh-credential.json, iroh-broker-cache.json) with broker revalidation and relay-token rotation.
    • Demo/tests: Docker harness proves EndpointID-only attach from a zero-port container; unit/integration tests cover grant verification and a real-iroh round trip.
  • Bug Fixes

    • Broker client: enforce https (or loopback http) for broker/Stack origins and disable redirects to protect bearer tokens.
    • Relay rotation: exponential cooldown after failed mints (capped) to stay within mint quotas.
    • Identity files: fsync the parent directory after atomic writes to avoid loss on power failure.
    • Listener: bound liveness reads and cancel grant-expiry timers when connections close.
    • CLI: treat -- as a separator.
    • Provider: handle transient rendezvous accept errors, return connection id from connection setup, open session streams outside locks, and read stream handshakes as raw bytes.
    • Demo harness: bounded line reads with timeouts, precise machine selection, gated listener start on session-socket readiness, validated sign-in fields, and fixed runtime state volume ownership.

Written for commit 4661b2a. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added an experimental secure relay transport for remote cmux-tui sessions.
    • Added device enrollment, authenticated listening, machine connections, pairing, and session bridging.
    • Added managed relay discovery with regional endpoints and persistent device identity.
    • Added Docker-based headless deployment and an end-to-end attach demonstration.
  • Security
    • Added signed, time-limited access grants, credential protection, admission checks, and connection revalidation.
  • Documentation
    • Documented transport behavior, deployment requirements, and Stage 1 acceptance criteria.

azooz2003-bit and others added 2 commits August 3, 2026 21:19
Stage 1 of the iroh TUI transport program (docs/iroh-tui-transport-stage1.md):
a sidecar that makes a cmux-tui session reachable by dialing its EndpointID
alone through the cmux account device registry and the managed relay fleet.

- listen: registers the device's (user, deviceId, tag) binding slot with the
  trust broker, binds a Minimal-preset endpoint on the verified relay catalog
  (never n0 defaults), and admits only connections whose first stream carries
  a broker-signed cmux/tui/1 pair grant naming this exact acceptor and the
  TLS-authenticated initiator. Admitted streams bridge byte-for-byte to the
  session Unix socket (unchanged protocol v10 JSON-lines, relay-stdio
  authority model). Broker state revalidates every 30s; grant expiry closes
  idle sessions; relay credentials rotate lazily via remove/insert_relay.
- enroll: exchanges a one-use provisioning token for a Stack session
  credential (POST /api/devices/iroh/enroll), mints the persistent Ed25519
  endpoint key + deviceId, and registers the slot; restarts re-register the
  same slot idempotently.
- provider: machine-provider v1 over stdio for
  cmux-tui --machine-provider-command. Control owns discovery, pair grants,
  and the single iroh endpoint; per-ticket stream processes rendezvous over a
  private unix socket so no second endpoint binds the same key.

Broker counterpart (linux platform, TUI grant profile, enrollment routes) is
#9515.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dockerfile builds a headless cmux-tui + cmux-tui-iroh image (zig for
libghostty-vt; a bind-mounted plain resolv.conf works around zig's
ResolvConfParseFailed on Docker-generated resolver files). run-demo.sh
drives the stage-1 acceptance: zero-published-port container enrolls with a
one-use token, registers, and listens; the Mac side enrolls its own
identity and attaches purely by EndpointID through the broker registry
(attach-once.py exercises the exact machine-provider v1 + admission +
protocol v10 seams the TUI uses); detach/reattach and a container restart
re-registering the same slot are asserted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@socket-security

socket-security Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​reqwest@​0.12.287910094100100
Addedcargo/​ed25519-dalek@​2.2.010010093100100

View full report

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the cmux-tui-iroh sidecar with broker enrollment, persistent identities, authenticated Iroh listener and machine-provider roles, relay management, Unix-socket bridging, and Docker-based acceptance tooling.

Changes

Iroh transport sidecar

Layer / File(s) Summary
Workspace, state, relay, and time foundations
cmux-tui/Cargo.toml, cmux-tui/crates/cmux-tui-iroh/*
Adds the workspace crate, persistent device records, hardened private-file storage, relay catalog handling, and expiry parsing.
Broker enrollment and CLI wiring
cmux-tui/crates/cmux-tui-iroh/src/{broker,endpoint,enroll,main}.rs
Adds token enrollment, device registration, discovery, relay-token refresh, endpoint dialing, enrollment workflow, and CLI commands.
Grant admission and listener bridging
cmux-tui/crates/cmux-tui-iroh/src/{grant,listen}.rs
Adds signed pair-grant validation, connection admission, broker revalidation, connection limits, and bidirectional Unix-socket bridging.
Machine-provider control and stream protocols
cmux-tui/crates/cmux-tui-iroh/src/provider.rs
Adds provider control and stream roles, machine discovery, pair-grant connections, one-use rendezvous tickets, and stdio forwarding.
Docker build and acceptance workflow
.dockerignore, cmux-tui/scripts/iroh-docker-demo/*
Adds the container image, entrypoint, enrollment-token minting, attach harness, resolver configuration, and restart-based acceptance workflow.
Transport specification and stage-1 design
cmux-tui/spec/transports.md, docs/iroh-tui-transport-stage1.md
Documents the relay transport, sidecar architecture, provider workflow, required integrations, and acceptance criteria.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Provider
  participant Broker
  participant Listener
  participant SessionSocket
  Client->>Provider: Request machine snapshot and open_machine
  Provider->>Broker: Discover machine and create pair grant
  Provider->>Listener: Dial endpoint and submit grant
  Listener->>SessionSocket: Open local session
  Listener-->>Provider: Return admission acknowledgment
  Provider-->>Client: Return transport stream
  Client->>Provider: Send session commands
  Provider<<->>SessionSocket: Forward session bytes
Loading

Possibly related PRs

  • manaflow-ai/cmux#9116: Updates Swift iroh-ffi relay failover and rotation behavior, while this PR adds the Rust cmux-tui-iroh transport.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (3 errors, 1 warning)

Check name Status Explanation Resolution
Cmux No Hacky Sleeps ❌ Error The PR adds fixed wall-clock polling: entrypoint.sh sleeps 0.2s for socket readiness, run-demo.sh sleeps 2s for Docker logs, and attach-once.py sleeps 1s before screen polls. Replace sleeps with owner-driven readiness or completion events, such as socket/process notifications, Docker log/event waits, and a protocol event for command output.
Cmux Algorithmic Complexity ❌ Error provider.rs:414-423 filters and rebuilds all discovered bindings for every Snapshot/SelectScope socket request, with no bound or derived snapshot cache. Cache the projected snapshot by discovery revision, or build it once when discovery changes; add an explicit bound or measurement for about 1000 account records.
Cmux User-Facing Error Privacy ❌ Error The CLI prints arbitrary broker error JSON via compact_error, propagates raw errors through provider responses, and exposes an enrollment environment variable and API route in an error. Use generic product-facing errors, redact all upstream bodies and credentials, and keep provider, route, environment, and raw error details in internal logs.
Docstring Coverage ⚠️ Warning Docstring coverage is 53.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (21 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed The full PR diff against main contains no .swift files or Swift actor-isolation declarations; it only changes Rust, scripts, configuration, and documentation.
Cmux Swift Blocking Runtime ✅ Passed The PR changes no Swift files versus main; all changed production code is Rust or scripts, so the Swift blocking-runtime check is not applicable.
Cmux Browser Automation Off-Main ✅ Passed The full PR delta adds no changes to either policy target file and contains no browser/WebKit automation symbols, so the check is not applicable.
Cmux Expensive Synchronous Load ✅ Passed The full PR diff contains only Docker, Cargo/Rust, scripts, and documentation paths; it contains no Swift files, so this Swift-specific check is not applicable.
Cmux Cache Substitution Correctness ✅ Passed The PR adds Rust transport code and demo scripts; its only JavaScript file mints enrollment tokens and does not replace an authoritative read with a cache in a persistence, history, undo, or snapsh...
Cmux Swift Concurrency ✅ Passed The pull request diff contains no Swift files or Swift code; it changes Rust, scripts, Docker, and documentation only.
Cmux Swift @Concurrent ✅ Passed The HEAD commit changes only Rust, scripts, Docker, and Markdown files; it contains no Swift paths or Swift concurrency annotations.
Cmux Swift Package Boundaries ✅ Passed The full PR range changes only Rust, Docker, Python, shell, JavaScript, JSON, and Markdown files; it introduces no Swift, SwiftPM, Xcode, or app-target changes.
Cmux Swiftpm Lockfiles ✅ Passed The PR diff contains no SwiftPM, Xcode project, .gitignore, or workflow changes; its dependency changes are Rust Cargo files only, so the SwiftPM lockfile rule does not apply.
Cmux Swift Logging ✅ Passed PASS: The parent-to-HEAD diff contains no Swift files or Swift logging changes; it only changes Rust, scripts, Docker, and documentation.
Cmux Full Internationalization ✅ Passed The PR touches no Swift, catalog, web, locale, or message files; its text is Rust CLI/provider protocol output, diagnostics, config, scripts, and developer/operational docs.
Cmux Swiftui State Layout ✅ Passed The complete PR diff contains no Swift, SwiftUI, or SwiftUI state/layout changes; it only adds Rust, scripts, docs, and .dockerignore files.
Cmux Architecture Rethink ✅ Passed The commit changes only Rust, scripts, Docker, and documentation; it contains no Swift paths or Swift hunks, so this Swift architecture check is not applicable.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR diff against main contains no Swift files or standalone cmux window changes, so the auxiliary-window close-shortcut rule is not applicable.
Cmux Source Artifacts ✅ Passed All changed paths are intentional Rust source, manifests, relay config, Docker/build config, acceptance scripts, or durable docs; no logs, screenshots, caches, temp folders, or build artifacts appear.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The complete PR diff from merge-base 840f8c0 to HEAD contains no Swift files under production Sources; no test/debug seam was added.
Cmux No Ambient Global State ✅ Passed The PR diff contains Rust, Python, shell/JS, Docker, and Markdown files, with zero Swift paths; the production-Swift-only rule is not applicable.
Title check ✅ Passed The title clearly summarizes the primary change: a broker-registered Iroh transport sidecar for cmux-tui.
Description check ✅ Passed The description clearly covers the change, rationale, testing, demo evidence, dependencies, and deferred work, although several template sections are omitted.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-iroh-tui-transport

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 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: 19

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

Inline comments:
In `@cmux-tui/crates/cmux-tui-iroh/src/broker.rs`:
- Around line 39-56: Validate the broker and stack origins in
BrokerConfig::resolve so credential-bearing requests only use HTTPS, rejecting
unsafe --broker, CMUX_TUI_IROH_BROKER, or CMUX_TUI_IROH_STACK_BASE values before
requests proceed. In cmux-tui/crates/cmux-tui-iroh/src/broker.rs lines 39-56,
enforce this origin validation; in cmux-tui/crates/cmux-tui-iroh/src/enroll.rs
lines 37-40, apply the validation before the enrollment POST and disable
redirects on the trusted reqwest client used for authenticated broker calls.

In `@cmux-tui/crates/cmux-tui-iroh/src/endpoint.rs`:
- Around line 85-93: Update the relay-rotation loop around fresh_relay_token to
track consecutive rotation failures and apply increasing backoff before retrying
after an error. Ensure the retry delay keeps mint attempts within the documented
limit of 3 per 10 minutes, while resetting the failure counter and normal retry
behavior after a successful token rotation.

In `@cmux-tui/crates/cmux-tui-iroh/src/files.rs`:
- Around line 103-107: Update the successful rename flow around fs::rename to
open the parent directory of path and call sync_all after the replacement
completes. Propagate any directory-open or sync_all failure with appropriate
context, while preserving temporary-file cleanup and the existing rename error
handling.

In `@cmux-tui/crates/cmux-tui-iroh/src/listen.rs`:
- Around line 221-233: Update the watchdog reader setup in the tokio::spawn
block to wrap admission_recv with take(ADMISSION_MAX_BYTES as u64) before
constructing BufReader, so read_line is bounded during buffering; retain the
existing sink length check and connection-close behavior.
- Around line 235-241: Update the expiry task spawned near expiry_connection to
select between the grant-expiry sleep and the connection’s close event, exiting
immediately when the connection closes and only calling close with “grant
expired” when the timer wins. Preserve the existing expiry delay and
connection-close behavior.

In `@cmux-tui/crates/cmux-tui-iroh/src/main.rs`:
- Around line 88-105: Update Flags::parse to recognize a bare "--" as the
end-of-flags separator: stop interpreting subsequent arguments as flags and
collect the separator’s trailing arguments in positional. Preserve existing
value-flag, boolean-flag, unknown-flag, and positional handling before the
separator, including provider commands such as control.

In `@cmux-tui/crates/cmux-tui-iroh/src/provider.rs`:
- Around line 143-160: Update the rendezvous accept loop around
rendezvous.accept() so recoverable accept errors are logged and the loop
continues instead of breaking. Preserve termination for unrecoverable errors by
distinguishing the error cases appropriately, ensuring the spawned transport
listener remains available for later stream connections.
- Around line 465-475: Update ensure_connection to return
anyhow::Result<String>, yielding the existing surviving connection id on its
early-return path and the inserted connection_id after creation. In
open_machine, use the returned id directly from ensure_connection and remove the
state_guard lookup, map search, and expect panic.
- Around line 640-664: In the handshake setup block, clone the machine
connection or required connection handle while the ControlState guard is held,
then exit the state.lock().await scope before calling open_bi(). Await
machine.connection.open_bi() only after the mutex guard has been dropped, while
preserving ticket validation, removal, and one-use semantics inside the critical
section.
- Around line 740-761: Update run_stream_blocking to collect the handshake in a
Vec<u8> and append each received byte unchanged, while retaining the
maximum-size validation. Parse the handshake with serde_json::from_slice(&line),
and forward the exact bytes via socket.write_all(&line) followed by the existing
separate newline write.

In `@cmux-tui/crates/cmux-tui-iroh/src/relays.rs`:
- Around line 76-83: Update the canonical path in
embedded_catalog_matches_committed_source_of_truth to resolve to the committed
repository catalog under config/iroh/managed-relay-catalog.json. Ensure the test
reads that source-of-truth file rather than an in-crate path, so missing-path
handling cannot silently skip drift validation.

In `@cmux-tui/scripts/iroh-docker-demo/attach-once.py`:
- Around line 63-75: Replace the blocking stdout.readline calls in
ControlClient.request and SessionStream.command with one shared bounded-read
helper that enforces the remaining timeout and raises the caller-specific
timeout error (“{method} timed out” or “{cmd} timed out”) when no complete line
arrives. Apply the helper at cmux-tui/scripts/iroh-docker-demo/attach-once.py
lines 63-75 and 123-134; both sites should use the same timeout-aware behavior.
- Around line 218-219: Implement actual execution for the --send-command path in
the attach-once harness by routing the command to the attached surface before
continuing, rather than only logging that it was skipped. Update the relevant
command-flow logic around args.send_command and ensure run-demo.sh’s invocation
exercises the command successfully; if wiring is not feasible, remove the flag
usage and document the missing stage-1 capability in
docs/iroh-tui-transport-stage1.md.
- Line 184: Update the machine-list formatting expression in the logging path to
treat each machine’s subtitle as optional, matching the handling at line 190.
Use the same fallback behavior for missing subtitle keys so logging continues
and tag matching can run.
- Around line 186-193: Update the target selection logic around the machines
iterator to match args.machine_tag against each machine descriptor’s identifier
or binding-id field rather than display_name or subtitle. Detect multiple
matching rows and fail instead of selecting the first match, while preserving
the existing no-match handling.

In `@cmux-tui/scripts/iroh-docker-demo/entrypoint.sh`:
- Around line 26-32: Replace the fixed sleep-based polling in entrypoint.sh with
an explicit readiness signal from the headless cmux-tui process, and only start
cmux-tui-iroh listen after that signal confirms the session socket is available.
If readiness is not observed before the timeout, exit nonzero and do not invoke
the listener.

In `@cmux-tui/scripts/iroh-docker-demo/mint-enrollment-token.mjs`:
- Around line 59-69: Validate the parsed sign-in response in the mint-enrollment
flow before constructing the request, requiring a usable session.access_token
(and the existing refresh token if needed). If the token is missing or the
response shape is invalid, fail immediately with a clear sign-in error instead
of sending an authorization header containing an undefined value; keep the
valid-session mint request unchanged.

In `@cmux-tui/scripts/iroh-docker-demo/run-demo.sh`:
- Around line 111-112: Remove the python3-based assignment from the binding ID
lookup and retain a single grep/sed read path in the BINDING_BEFORE
initialization. Preserve the existing fallback behavior while eliminating the
unavailable runtime dependency.

In `@docs/iroh-tui-transport-stage1.md`:
- Line 28: Update the Minimal preset documentation to identify
cmux-tui/crates/cmux-tui-iroh/relay-catalog.json as the embedded runtime
catalog, noting it is compiled from config/iroh/managed-relay-catalog.json.
Revise the crate documentation in relays.rs to describe the same
include_str!-based embedded catalog and canonical-source relationship.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a632518e-f158-4717-988d-03db50d3444b

📥 Commits

Reviewing files that changed from the base of the PR and between a9c351b and 6b51549.

⛔ Files ignored due to path filters (1)
  • cmux-tui/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • .dockerignore
  • cmux-tui/Cargo.toml
  • cmux-tui/crates/cmux-tui-iroh/Cargo.toml
  • cmux-tui/crates/cmux-tui-iroh/relay-catalog.json
  • cmux-tui/crates/cmux-tui-iroh/src/broker.rs
  • cmux-tui/crates/cmux-tui-iroh/src/endpoint.rs
  • cmux-tui/crates/cmux-tui-iroh/src/enroll.rs
  • cmux-tui/crates/cmux-tui-iroh/src/files.rs
  • cmux-tui/crates/cmux-tui-iroh/src/grant.rs
  • cmux-tui/crates/cmux-tui-iroh/src/identity.rs
  • cmux-tui/crates/cmux-tui-iroh/src/listen.rs
  • cmux-tui/crates/cmux-tui-iroh/src/main.rs
  • cmux-tui/crates/cmux-tui-iroh/src/provider.rs
  • cmux-tui/crates/cmux-tui-iroh/src/relays.rs
  • cmux-tui/crates/cmux-tui-iroh/src/timefmt.rs
  • cmux-tui/scripts/iroh-docker-demo/Dockerfile
  • cmux-tui/scripts/iroh-docker-demo/attach-once.py
  • cmux-tui/scripts/iroh-docker-demo/build-resolv.conf
  • cmux-tui/scripts/iroh-docker-demo/entrypoint.sh
  • cmux-tui/scripts/iroh-docker-demo/mint-enrollment-token.mjs
  • cmux-tui/scripts/iroh-docker-demo/run-demo.sh
  • cmux-tui/spec/transports.md
  • docs/iroh-tui-transport-stage1.md

Comment thread cmux-tui/crates/cmux-tui-iroh/src/broker.rs Outdated
Comment thread cmux-tui/crates/cmux-tui-iroh/src/endpoint.rs Outdated
Comment thread cmux-tui/crates/cmux-tui-iroh/src/files.rs
Comment thread cmux-tui/crates/cmux-tui-iroh/src/listen.rs
Comment thread cmux-tui/crates/cmux-tui-iroh/src/listen.rs Outdated
Comment thread cmux-tui/scripts/iroh-docker-demo/attach-once.py Outdated
Comment thread cmux-tui/scripts/iroh-docker-demo/entrypoint.sh Outdated
Comment thread cmux-tui/scripts/iroh-docker-demo/mint-enrollment-token.mjs
Comment thread cmux-tui/scripts/iroh-docker-demo/run-demo.sh Outdated
Comment thread docs/iroh-tui-transport-stage1.md Outdated
Broker client: enforce https (or loopback http) for broker and Stack
origins and disable redirect following so bearer headers cannot leak to a
redirect target. Relay rotation: exponential cooldown after failed mints
(2^n minutes, capped 32) keeps a persistently down relay inside the
3-per-10-minute quota instead of burning it every 30s. Identity files:
fsync the parent directory after the atomic rename so power loss right
after enrollment cannot lose the registration authority. Listener: bound
the liveness-channel read during the read (take + set_limit), and cancel
the grant-expiry sleeper when its connection closes. Flags: treat -- as a
separator. Provider: survive transient rendezvous accept errors, return
the connection id from ensure_connection (drops the expect lookup), open
session streams outside the state lock so a slow machine cannot stall the
control loop, and collect the stream handshake as raw bytes so non-ASCII
frames are not re-encoded.

Demo harness: bounded line reads that honor timeouts (reader threads),
select machines by descriptor id or exact registered display name instead
of substring text, wire --send-command through protocol v10 send +
read-screen with a quote-split marker, gate the container listener on
session-socket readiness and fail closed, validate Stack sign-in fields in
the mint helper, drop the dead python3 branch, and fix the runtime image
volume ownership (chown the state dir before VOLUME). Docs: name the
embedded relay-catalog copy and its canonical source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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: 3

♻️ Duplicate comments (1)
cmux-tui/crates/cmux-tui-iroh/src/provider.rs (1)

154-160: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Do not retry every accept error with a fixed sleep.

A persistent accept error makes this detached task log every 200 ms indefinitely. The control loop remains active and can issue tickets although no rendezvous stream can connect.

Retry only recoverable errors such as Interrupted or ConnectionAborted. Propagate terminal errors to run_control. If resource exhaustion requires backoff, use a bounded cancellation-aware retry abstraction.

As per coding guidelines and path instructions, production socket recovery must not use fixed wall-clock waits as synchronization.

🤖 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 `@cmux-tui/crates/cmux-tui-iroh/src/provider.rs` around lines 154 - 160, Update
the accept-error handling in the rendezvous accept loop around the Err(error)
branch to retry only recoverable errors such as Interrupted or
ConnectionAborted, and propagate terminal errors to run_control instead of
continuing indefinitely. Remove the unconditional fixed 200ms sleep; if
resource-exhaustion backoff is needed, use a bounded cancellation-aware retry
mechanism.

Sources: Coding guidelines, Path instructions

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

Inline comments:
In `@cmux-tui/crates/cmux-tui-iroh/src/broker.rs`:
- Around line 77-83: Update the URL validation in the broker client setup around
the loopback detection: parse the URL with reqwest’s URL representation, reject
any username or password, and permit HTTP only when the parsed host is a
loopback address; remove the host.docker.internal exception. Ensure bracketed
IPv6 localhost (http://[::1]) remains allowed, reject userinfo such as
http://localhost:80@attacker.example, and add regressions covering both cases.
- Line 87: Update the validation error in the broker URL parsing flow to omit
the raw URL value. Keep the configuration name and required
HTTPS-or-loopback-HTTP guidance in the message, but remove the `{url:?}`
interpolation so query strings and userinfo cannot reach CLI output.

In `@cmux-tui/scripts/iroh-docker-demo/attach-once.py`:
- Around line 134-136: Replace the raw response interpolation in the
transport-handshake rejection raised by the reader logic in attach-once.py with
a stable product-level error message; if diagnostics are needed, log only a
bounded internal code rather than provider fields. In
cmux-tui/scripts/iroh-docker-demo/mint-enrollment-token.mjs lines 60-62, apply
the same redaction behavior to the corresponding handshake rejection site.

---

Duplicate comments:
In `@cmux-tui/crates/cmux-tui-iroh/src/provider.rs`:
- Around line 154-160: Update the accept-error handling in the rendezvous accept
loop around the Err(error) branch to retry only recoverable errors such as
Interrupted or ConnectionAborted, and propagate terminal errors to run_control
instead of continuing indefinitely. Remove the unconditional fixed 200ms sleep;
if resource-exhaustion backoff is needed, use a bounded cancellation-aware retry
mechanism.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0f4af54c-668a-4b4d-99af-0b12b96c13b7

📥 Commits

Reviewing files that changed from the base of the PR and between 6b51549 and 4661b2a.

📒 Files selected for processing (14)
  • cmux-tui/crates/cmux-tui-iroh/src/broker.rs
  • cmux-tui/crates/cmux-tui-iroh/src/endpoint.rs
  • cmux-tui/crates/cmux-tui-iroh/src/enroll.rs
  • cmux-tui/crates/cmux-tui-iroh/src/files.rs
  • cmux-tui/crates/cmux-tui-iroh/src/listen.rs
  • cmux-tui/crates/cmux-tui-iroh/src/main.rs
  • cmux-tui/crates/cmux-tui-iroh/src/provider.rs
  • cmux-tui/crates/cmux-tui-iroh/src/relays.rs
  • cmux-tui/scripts/iroh-docker-demo/Dockerfile
  • cmux-tui/scripts/iroh-docker-demo/attach-once.py
  • cmux-tui/scripts/iroh-docker-demo/entrypoint.sh
  • cmux-tui/scripts/iroh-docker-demo/mint-enrollment-token.mjs
  • cmux-tui/scripts/iroh-docker-demo/run-demo.sh
  • docs/iroh-tui-transport-stage1.md

Comment on lines +77 to +83
let loopback = url.strip_prefix("http://").is_some_and(|rest| {
let host = rest.split(['/', ':']).next().unwrap_or("");
host == "localhost"
|| host == "127.0.0.1"
|| host == "[::1]"
|| host == "host.docker.internal"
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Sensitive Data Exposure (CWE-319): Cleartext Transmission of Sensitive Information

Reachability: External

Reachability path
● Entry
  cmux-tui/crates/cmux-tui-iroh/src/provider.rs:49
  run
│
▼
● Hop
  cmux-tui/crates/cmux-tui-iroh/src/enroll.rs:24
  run
│
▼
● Hop
  cmux-tui/crates/cmux-tui-iroh/src/main.rs
│
▼
● Hop
  cmux-tui/crates/cmux-tui-iroh/src/listen.rs:63
  run: Register (heartbeat) this device's slot and refresh the verification keys.
│
▼
● Hop
  cmux-tui/crates/cmux-tui-iroh/src/endpoint.rs:65
  spawn_relay_maintenance: Exponential backoff between rotation attempts keeps a persistently
│
▼
● Sink
  cmux-tui/crates/cmux-tui-iroh/src/broker.rs

Validate the parsed authority before allowing HTTP.

split(['/', ':']) reads userinfo as the host. http://localhost:80@attacker.example passes this loopback branch, but the request targets attacker.example. BrokerClient::enroll then sends the enrollment token, and authenticated calls send bearer and refresh credentials over cleartext. An attacker needs to control a configured broker or Stack origin. The host.docker.internal exception also permits a non-TLS hop.

Parse the URL, reject userinfo, and allow HTTP only for parsed loopback endpoints. Add regressions for this input and http://[::1]. Reqwest’s URL representation separates userinfo from host. (docs.rs)

🤖 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 `@cmux-tui/crates/cmux-tui-iroh/src/broker.rs` around lines 77 - 83, Update the
URL validation in the broker client setup around the loopback detection: parse
the URL with reqwest’s URL representation, reject any username or password, and
permit HTTP only when the parsed host is a loopback address; remove the
host.docker.internal exception. Ensure bracketed IPv6 localhost (http://[::1])
remains allowed, reject userinfo such as http://localhost:80@attacker.example,
and add regressions covering both cases.

if loopback {
return Ok(());
}
bail!("{what} {url:?} must be https (or loopback http for local development)");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== broker.rs relevant section =="
sed -n '1,140p' cmux-tui/crates/cmux-tui-iroh/src/broker.rs | cat -n

echo
echo "== CLI args around broker option =="
rg -n "broker|BrokerConfig|struct .*Args|clap|Parser|ValueEnum" cmux-tui/crates/cmux-tui-iroh/src/main.rs cmux-tui/crates/cmux-tui-iroh/src -g '*.rs'

echo
echo "== anyhow and error contexts/usages =="
rg -n "Bail!"|rg -n "context\\(|format!\\(|print|println|eprintln|bail!(" cmux-tui/crates/cmux-tui-iroh/src/main.rs cmux-tui/crates/cmux-tui-iroh/src -g '*.rs' | head -n 200

Repository: manaflow-ai/cmux

Length of output: 16997


🌐 Web query:

Rust std::string::String Debug {:?} includes quotes quotes escaped

💡 Result:

In Rust, the Debug trait implementation for String (and str) is specifically designed to provide a programmer-facing, unambiguous representation of the string's content [1][2][3]. When you use the {:?} format specifier, the resulting output automatically includes double quotes surrounding the string and escapes any non-printable characters or control characters [1][4]. Key aspects of this behavior include: 1. Quotes: The output is wrapped in double quotes (e.g., "hello") to clearly delimit the start and end of the string value [1][3]. 2. Escaping: Non-printable characters, control characters, and certain Unicode codepoints are escaped [1][4]. For example, a newline character becomes \n, a tab becomes \t, and double quotes within the string are escaped as \" [4][3]. 3. Purpose: This approach is intended for debugging [2][3]. By including quotes and escaping hidden characters, it allows developers to distinguish between empty strings, strings with whitespace, and strings containing special characters, which would otherwise be difficult or impossible to see with the Display trait ({}) [1][3]. This behavior is mandated by the Debug implementation for str, which String uses via dereferencing [5][2]. The escaping logic is specifically designed to make the string's content explicit and easy to inspect during development [1][4].

Citations:


Sensitive Data Exposure (CWE-532): Insertion of Sensitive Information into Log File

Reachability: External

Reachability path
● Entry
  cmux-tui/crates/cmux-tui-iroh/src/provider.rs:49
  run
│
▼
● Hop
  cmux-tui/crates/cmux-tui-iroh/src/enroll.rs:24
  run
│
▼
● Hop
  cmux-tui/crates/cmux-tui-iroh/src/main.rs
│
▼
● Hop
  cmux-tui/crates/cmux-tui-iroh/src/listen.rs:63
  run: Register (heartbeat) this device's slot and refresh the verification keys.
│
▼
● Hop
  cmux-tui/crates/cmux-tui-iroh/src/endpoint.rs:65
  spawn_relay_maintenance: Exponential backoff between rotation attempts keeps a persistently
│
▼
● Sink
  cmux-tui/crates/cmux-tui-iroh/src/broker.rs

Redact the rejected broker URL from CLI errors.

This error comes from parsing user-provided broker arguments/env values and propagates the raw {url:?} value through CLI output. The broker URL can include query strings or userinfo, so print only the configuration name and the required scheme.

Proposed fix
-    bail!("{what} {url:?} must be https (or loopback http for local development)");
+    bail!("{what} must be https (or a permitted loopback HTTP endpoint for local development)");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bail!("{what} {url:?} must be https (or loopback http for local development)");
bail!("{what} must be https (or a permitted loopback HTTP endpoint for local development)");
🤖 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 `@cmux-tui/crates/cmux-tui-iroh/src/broker.rs` at line 87, Update the
validation error in the broker URL parsing flow to omit the raw URL value. Keep
the configuration name and required HTTPS-or-loopback-HTTP guidance in the
message, but remove the `{url:?}` interpolation so query strings and userinfo
cannot reach CLI output.

Source: Coding guidelines

Comment on lines +134 to +136
result = json.loads(self.reader.readline(30.0, "transport handshake"))
if not result.get("accepted"):
raise RuntimeError(f"transport handshake rejected: {result}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files:\n'
git ls-files | grep -E '(^|/)attach-once\.py$|(^|/)mint-enrollment-token\.mjs$|(^|/)entrypoint\.sh$|(^|/)cmux-tui-iroh/src/main\.rs$' || true

printf '\nattach-once outline:\n'
ast-grep outline cmux-tui/scripts/iroh-docker-demo/attach-once.py --view compact || true

printf '\nattach-once relevant lines:\n'
cat -n cmux-tui/scripts/iroh-docker-demo/attach-once.py | sed -n '1,90p;120,150p;166,286p'

printf '\nentrypoint main and attach-once callers:\n'
cat -n cmux-tui/scripts/iroh-docker-demo/entrypoint.sh | sed -n '1,220p'

printf '\nmint-enrollment relevant lines:\n'
cat -n cmux-tui/scripts/iroh-docker-demo/mint-enrollment-token.mjs | sed -n '1,110p'

printf '\nTop-level Python error handling/search:\n'
rg -n "RuntimeError|argparse|print Exception|traceback|except Exception|catch Exception|process\.stderr" cmux-tui/scripts/iroh-docker-demo -S || true

printf '\nPython behavioral probe: exception string includes raw payload fields:\n'
python3 - <<'PY'
import json
result = {"accepted": False, "provider_id": "prov-SECRET", "status": {"code": 500, "message": "bad token"}}
msg = f"transport handshake rejected: {result}"
print(msg)
print("secret_payload_in_exception=", "prov-SECRET" in msg)
PY

printf '\nPython argument parser behavior probe with RuntimeError (read-only mock path):\n'
python3 - <<'PY'
import io, sys
try:
    with open(__import__('tempfile').NamedTemporaryFile(delete=False).name) as f:
        raise TypeError("boom")
except Exception as exc:
    print(exc)
PY

Repository: manaflow-ai/cmux

Length of output: 18391


Information Disclosure (CWE-209): Generation of Error Message Containing Sensitive Information

Reachability path
● Entry
  cmux-tui/crates/cmux-tui-iroh/src/endpoint.rs:65
  spawn_relay_maintenance: Exponential backoff between rotation attempts keeps a persistently
│
▼
● Sink
  cmux-tui/scripts/iroh-docker-demo/attach-once.py

Avoid leaking raw handshake response data in attach-once.py.

RuntimeError(f"transport handshake rejected: {result}") includes provider handshake fields in the Python error string. Use a stable product-level message for the exception and log only a bounded internal diagnostic code if needed.

cmux-tui/scripts/iroh-docker-demo/attach-once.py#L134-L136

🧰 Tools
🪛 Ruff (0.16.0)

[warning] 136-136: Avoid specifying long messages outside the exception class

(TRY003)

📍 Affects 2 files
  • cmux-tui/scripts/iroh-docker-demo/attach-once.py#L134-L136 (this comment)
  • cmux-tui/scripts/iroh-docker-demo/mint-enrollment-token.mjs#L60-L62
🤖 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 `@cmux-tui/scripts/iroh-docker-demo/attach-once.py` around lines 134 - 136,
Replace the raw response interpolation in the transport-handshake rejection
raised by the reader logic in attach-once.py with a stable product-level error
message; if diagnostics are needed, log only a bounded internal code rather than
provider fields. In cmux-tui/scripts/iroh-docker-demo/mint-enrollment-token.mjs
lines 60-62, apply the same redaction behavior to the corresponding handshake
rejection site.

Source: Coding guidelines

@lawrence703

Copy link
Copy Markdown
Collaborator

Closing this superseded stage-1 sidecar. Head 4661b2a446354aca4af470a9579fcb7903d5b948 depends on PR 9515 and its cmux-tui-iroh implementation is absent from current main.
The transport/auth direction moved to merged PR 10889, current PR 11431, and relay alternatives PR 10963 / PR 11071. Re-cut only after the current broker/auth contract and security review. No unique safe delta is retained here.

@lawrencecchen

Copy link
Copy Markdown
Contributor

Closing as superseded by the current relay transport architecture. Its parent Linux enrollment work is closed, and the sidecar protocol does not apply cleanly to current main.

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.

3 participants