Add scriptable pane control via local UDS API and wp CLI#10255
Add scriptable pane control via local UDS API and wp CLI#10255Nikitzu wants to merge 4 commits intowarpdotdev:masterfrom
wp CLI#10255Conversation
|
Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Nikitzu.
|
There was a problem hiding this comment.
Overview
This PR adds a local IPC control plane and wp CLI for scripting pane operations in Warp.
Concerns
- The published socket address is written with default filesystem permissions and points at a socket under
/tmp, which does not enforce the same-user assumption for a mutating API that can write to PTYs and close panes. - The server accepts unbounded IPC payloads into an unbounded dispatch queue, making local denial of service possible with large or rapid
send-textrequests. close-panereports success for stale or nonexistent pane IDs because the helper delegates toclose_pane, which silently no-ops.
Security
- Harden address/socket publication with a private per-user directory or 0600 address file plus authentication before enabling mutating commands.
- Add request-size and queue limits so local clients cannot exhaust Warp memory or main-thread processing.
Verdict
Found: 0 critical, 3 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
ee4a0e9 to
8844045
Compare
|
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have the users @Nikitzu on file. In order for us to review and merge your code, each contributor must visit https://cla.warp.dev to read and agree to our CLA. Once you have done so, please comment |
|
@cla-bot check |
|
The cla-bot has been summoned, and re-checked this pull request! |
Adds a UDS-based local control plane plus a sibling `wp` CLI binary, giving Warp the same scripting affordance that iTerm2 ships through `it2cli`, kitty through `kitty @`, and wezterm through `wezterm cli`. External processes (build automations, dotfile managers, productivity tooling) can split panes, send text, list and close panes against the running Warp without going through the keyboard / event system. Architecture mirrors `iTermAPIServer`: * New crate `warp_local_api` defines the wire protocol — typed `LocalApiRequest` / `LocalApiResponse` enums, `ipc::Service` impl, socket-address publish path under the platform's data-local dir. * `app/src/local_api.rs` registers `LocalApiServer` as a singleton entity at startup. Hosts `ipc::Server` from `crates/ipc` on the background executor; forwards each request over an `async_channel` to the main thread, where `spawn_stream_local` dispatches it into the active workspace's active pane group and replies via a oneshot. This pattern avoids holding `TerminalModel` locks across awaits (the documented deadlock hazard from `WARP.md`). * New crate `wp` is a thin clap-based client that reads the published socket address, connects via `ipc::Client`, and calls `service_caller::<LocalApiService>`. Subcommands: `ping`, `split <dir>`, `send-text [--pane ID] TEXT`, `list-panes`, `active-pane`, `close-pane <ID>`. * `pane_group::PaneGroup` gains three `pub(crate)` helpers used by the in-app handler: `local_api_split_active_pane`, `local_api_terminal_pane_ids`, `local_api_close_pane`. * `pane_group::pane::TerminalPaneId` gains `pub(crate)` `to_local_api_string` / `parse_local_api` for stable wire serialization round-tripping with the CLI. Verified end-to-end: `wp ping → pong`, `wp split right` produces a new Warp pane, `wp send-text --pane <id>` injects bytes into the target terminal's PTY. `cargo fmt` + `cargo clippy --all-targets --tests -- -D warnings` clean across the new and modified crates. CHANGELOG-NEW-FEATURE: Add scriptable pane control via local UDS API and `wp` CLI.
8844045 to
8742d5a
Compare
|
@oz-agent check |
|
/oz-review |
There was a problem hiding this comment.
Overview
Adds a new local IPC control server plus a wp CLI for pane operations such as split, send-text, list, active-pane, and close-pane.
Security
- The server is registered on every app startup, and possession of a 0600 same-UID cookie file is enough to inject bytes into terminal PTYs or close panes. This needs explicit user opt-in and/or a stronger authorization model before being enabled by default.
Concerns
- The new module is compiled unconditionally but imports Unix-only filesystem APIs, which breaks Windows builds.
- The socket path is fixed globally, so concurrent channels/instances can remove and rebind each other's local API endpoint.
Verdict
Found: 1 critical, 2 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
Address oz review on #10255: - Gate `local_api` module and its singleton registration behind `cfg(unix)`. The module imports `std::os::unix::fs` and uses chmod-based 0600 hardening, so it can't compile on Windows. Until a Windows transport / ACL-based permission model exists, exclude the module on non-Unix. - Make the server opt-in via `WARP_ENABLE_LOCAL_API`. Until per-client authorization exists, exposing PTY writes and pane close on every same-UID launch is too broad a default; users who want it now flip the env, others stay unaffected. - Namespace the socket and address file by `ChannelState::data_domain()`, and put the PID in the socket filename. Lets dev / preview / stable / oss channels run side-by-side without the second-launched instance removing the first's bind path. The address file remains per-domain so child shells can still discover their own Warp's socket. - Plumb `WARP_LOCAL_API_ADDRESS` / `WARP_LOCAL_API_DOMAIN` into `warp_local_api::address_publish_path` for the wp CLI: env-overridable full path first, channel-domain default second, hardcoded `dev.warp.Warp` as last-resort fallback for ad-hoc invocations. cargo fmt --all -- --check, cargo check + cargo clippy --all-targets --tests -D warnings clean for warp_local_api, wp, and warp.
|
Pushed Critical — Important — auto-start on every launch ( Important — fixed socket path across channels/instances ( Earlier-review items already in
Verification on this commit:
/oz-review @oz-for-oss please take another look. |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds a Unix-domain-socket local control plane and a wp CLI for pane operations. The overall shape follows existing IPC patterns, but the current diff has correctness and security issues around opt-in semantics, address discovery, and pre-auth payload handling.
Concerns
WARP_ENABLE_LOCAL_API=0or any other value still enables the mutating local API despite the documented=1opt-in.wpdefaults todev.warp.Warpwhile the server publishes underChannelState::data_domain(), so OSS/dev/profiled builds cannot find the address file by default and the promised shell env propagation is not present in this diff.
Security
- The
SendTextsize check happens after the generic IPC layer has already read, allocated, and deserialized the frame, so an unauthenticated local client can still force large allocations before the cookie is checked.
Verdict
Found: 0 critical, 3 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
…iscover address file - WARP_ENABLE_LOCAL_API now requires a truthy value (1/true/yes/on); =0/=false/empty leave it disabled. - ipc::ServerBuilder gains with_max_request_bytes; receive_message rejects oversized frames at the framing layer before any payload read or deserialize, so an unauthenticated peer can't force a large allocation by lying about its size. Local API caps frames at MAX_SEND_TEXT_BYTES + 4 KiB envelope overhead. - wp resolves the address file via WARP_LOCAL_API_ADDRESS, then WARP_LOCAL_API_DOMAIN, then auto-discovers a single live channel under the data-local dir. Multiple live instances surface as an ambiguity error naming each candidate path. Removes the inaccurate doc claim that Warp injects WARP_LOCAL_API_ADDRESS into spawned shells (not implemented in this PR scope).
|
Pushed Important — Important —
Important — Verification on this commit (with the workspace
( /oz-review @oz-for-oss please take another look. |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR adds an opt-in local UDS control API and a wp CLI for scripting pane operations. The overall direction is clear, but the new unauthenticated IPC boundary needs stronger failure handling before merge.
Concerns
- The local API handler relies on typed IPC deserialization before cookie validation, so malformed service bytes can panic the handler before auth runs.
- The address-file writer can expose a fresh cookie through a previously loose file mode before it tightens permissions.
- Oversized-frame handling returns before consuming payload bytes, but the server loop continues on the same stream.
Security
- The two local API authentication/cookie handling issues are security-sensitive because this feature deliberately exposes mutating PTY and pane operations to sibling local processes.
Verdict
Found: 0 critical, 3 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
… write, close stream on oversized frame
- Replace .expect-panic in AnyServiceImpl deserialize with a fallible
signature; bad request bytes now flow through Response::Failure
instead of crashing the connection task. The cookie check still
runs on the typed envelope, but a malformed frame can no longer
panic the worker before auth.
- publish_address now writes through a sibling temp file opened with
create_new + .mode(0o600) and atomically renames over the target.
Previous code reused an existing inode, so OpenOptions::mode never
fired and the cookie could be world-readable for the window
between write_all and set_permissions.
- Promote ProtocolError::Other("frame too large") to a structured
ProtocolError::FrameTooLarge variant. The server loop closes the
connection on it because the announced payload bytes were never
consumed and the stream is misaligned; previous behaviour logged
and continued, leaving subsequent reads parsing payload bytes as
the next frame's header.
|
Pushed Important — typed deserialization could panic the handler before auth runs
The response-side Important — address-file writer could expose a fresh cookie under loose perms
The destination's old perms are irrelevant because the new inode replaces it; the cookie bytes are never resident in a file with looser-than-0600 mode. Important — oversized-frame handling desynced the stream Two coordinated changes:
Verification on this commit:
/oz-review @oz-for-oss please take another look. |
Description
Adds a UDS-based local control plane plus a sibling
wpCLI binary, giving Warp the same scripting affordance that iTerm2 ships throughit2cli, kitty throughkitty @, and wezterm throughwezterm cli. External processes (build automations, dotfile managers, productivity tooling) can split panes, send text, list and close panes against the running Warp without going through the keyboard / event system.Architecture
Mirrors
iTermAPIServer's "background socket reads, main-thread mutations" pattern.crates/warp_local_api/— wire types and theipc::Servicedefinition. TypedLocalApiRequest/LocalApiResponseenums, address-publish-path helper.app/src/local_api.rs—LocalApiServersingleton entity registered at startup. Hosts anipc::Serverfromcrates/ipc/on the background executor; forwards each request over anasync_channelto the main thread, wherespawn_stream_localdispatches it into the active workspace's active pane group and replies via a oneshot. This pattern avoids holdingTerminalModellocks across awaits, the documented deadlock hazard fromWARP.md.crates/wp/— thin clap-based client. Reads the published socket address, connects viaipc::Client, callsservice_caller::<LocalApiService>. Subcommands:ping,split <dir>,send-text [--pane ID] TEXT,list-panes,active-pane,close-pane <ID>.pane_group::PaneGroupgains threepub(crate)helpers used by the in-app handler:local_api_split_active_pane,local_api_terminal_pane_ids,local_api_close_pane.pane_group::pane::TerminalPaneIdgainspub(crate)to_local_api_string/parse_local_apifor stable wire serialization.Why
Every other terminal in the space (iTerm2, kitty, wezterm, alacritty + tmux) ships some external-control surface. Warp doesn't. This blocks scripts/automations that want to drive Warp from a sibling process: invoking a build in one pane while a watcher runs in another, opening a layout from a project's
Justfile, integrating with productivity tools, etc. Today the only options are AppleScript (extremely limited and macOS-only) and thewarp://deeplink scheme (one-shot, fire-and-forget, no return values). This PR adds the missing capability without breaking either of those surfaces.What this is not
This PR intentionally does not include:
v1namespace and protocol-version handshake should be added. Happy to do that here if reviewers prefer.Linked Issue
None — this is a net-new capability that didn't have an existing tracking issue. Happy to open one if the team prefers that workflow before review.
ready-to-specorready-to-implement.Screenshots / Videos
No UI changes. The verification flow looks like:
Testing
cargo fmt --all -- --check— clean.cargo clippy -p warp_local_api -p wp -p warp --all-targets --tests -- -D warnings— clean.warp-oss, ranwp ping → pong,wp split rightproduced a new Warp pane and printed its id,wp send-text --pane <id> "ls<newline>"injected the keystrokes into the target terminal's PTY,wp close-pane <id>removed it.ipc::ServerBuilderpattern asPluginHost(seeapp/src/plugin/app/mod.rs). Lock-discipline tested by exercising rapid split + send-text + close cycles — no deadlocks observed.I did not add automated tests in this PR because the request-dispatch path needs a running app context, and I didn't see an existing fixture for that pattern in the workspace. Happy to add
cargo testcoverage for the bincode wire format and any unit-testable helpers if reviewers point me at the right testing pattern.Agent Mode
CHANGELOG-NEW-FEATURE: Add scriptable pane control via local UDS API and
wpCLI, comparable to iTerm2'sit2cli.