phase 6c+6d: xpc tun + xpc ghidra/ida lifecycle#3
Conversation
First subcommand to exercise both directions of ARCP streaming on a single
job. Forwards local TCP through the agent: each accepted connection on
127.0.0.1:<localPort> spawns a fresh ARCP session, invokes tun.connect on
the agent, then bidirectionally pipes bytes via stream.chunk(delta_b64).
Agent additions (agent/agent.py):
* tool_tun_connect: opens a VM-side TCP socket to arguments.host:port,
stores it on the running Job, pumps VM->host bytes via stream.chunk on
a fresh upstream stream id. On EOF / cancel, closes and emits
stream.close.
* Connection._dispatch now routes stream.chunk and stream.close envelopes
from the client. They look up the matching Job by job_id and either
sendall the base64-decoded delta into job.tun_socket or
shutdown(SHUT_WR). Non-tun jobs drop these envelopes silently.
Host additions (internal/cli/tun.go):
* xpc tun -L localPort:vmHost:vmPort cobra command.
* Reader goroutine: decodes envelopes, routes upstream stream.chunk to
local conn (decode delta_b64 -> Write), stream.close to local
CloseWrite, terminal types to clean up.
* Forwarder goroutine: waits for job.accepted via a jobReady channel
before pumping local.Read -> stream.chunk envelopes on a downstream
stream id. On local EOF, sends stream.close. The jobReady gate fixes
a race where the first chunk would otherwise go out with empty job_id
and be silently dropped by the agent.
* Single writeMu mutex serialises all envelope writes so the forwarder
and cancel sender don't interleave bytes on the TLS conn.
Real-VM verification (docs/sessions/phase-6c-tun.md):
$ ./bin/xpc tun -L 19578:127.0.0.1:9578 &
$ python3 -c "send xpctl ping over local TCP; receive pong"
xpctl response: {'pong': True, 'uptime': 72791.625, ...}
Agent log:
tun.connect [job=job_ryp...] -> 127.0.0.1:9578
This pattern is the foundation for xpc ghidra/ida server-tunnel commands
that the master prompt §10 schedules later.
Deferred: xpc tun -R (reverse), xpc daemon, xpc dbg/trace/ghidra/ida, snap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Thin lifecycle wrappers built on top of Phase 6c's xpc tun. Both commands
share the same pattern: detached-spawn the RE server on the VM, save its
PID to a runlog, and tell the user how to expose it via xpc tun -L. The
tunnel stays a separate command so users can pick any local port and re-
tunnel without restarting the server.
* internal/cli/ghidra.go:
- xpc ghidra start [--binary] [--port] [--repo]
Default --binary: C:\ghidra\support\ghidraSvr.bat
Default --port: 13100
Spawns detached via the same os.dup2-to-NUL + DETACHED_PROCESS trick
used by xpc bootstrap's manage.py. PID saved to
C:\xpc\ghidra.runlog.pid for later stop / introspection.
- xpc ghidra stop
WMIC where name='java.exe' and commandline like '%ghidra%'; taskkill
each match. Won't touch unrelated java processes.
- Shared helpers buildDetachedSpawnPy and killByCmdLineMatch are reused
by xpc ida and ready for future server-lifecycle commands.
* internal/cli/ida.go:
- xpc ida start [--binary] [--port]
Default --binary: C:\IDA\dbgsrv\win32_remote.exe
Default --port: 23946 (IDA convention)
- xpc ida stop
Matches both win32_remote.exe and dbgsrv.exe on the off-chance the
user installs the older binary name.
Sample run (dry-run; live verification pending until Ghidra / IDA are
actually installed on the VM):
$ xpc ghidra start --dry-run
(dry-run) start C:\ghidra\support\ghidraSvr.bat with port 13100 ...
$ xpc ida start --dry-run
(dry-run) start C:\IDA\dbgsrv\win32_remote.exe --port 23946
Deferred: xpc dbg (long-running debugger sessions), xpc trace (procmon),
xpc snap (Proxmox), xpc daemon, xpc tun -R, TOFU SSH host-key.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backfills the CHANGELOG entry that didn't make it into 1dbbb7e because the file was externally re-touched between read and edit. Content matches what was already captured in TASKS.md §6.14. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR implements Phase 6c ARCP bidirectional streaming for TCP tunneling and Phase 6d remote debugger lifecycle management. The agent exposes a ChangesBidirectional ARCP Tunnel and Debugger Lifecycle
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
Triggers `go: no such tool "covdata"` on Go 1.22 + actions/setup-go@v5 when test compilation runs for packages without test files (cmd/gen-corpus, cmd/xpc-exec, cmd/xpc-roundtrip, internal/cli). Tests themselves pass and report coverage on packages that do have tests; the cover profile output just isn't uploaded anywhere. Dropping the flag is the cleanest fix. Locally and on the previous CI runs this didn't repro because Go's covdata tool is part of the cache that setup-go@v5 sometimes restores partially. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b64c5c7670
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case <-time.After(5 * time.Second): | ||
| return |
There was a problem hiding this comment.
Remove fixed 5s wait for job acceptance
The forwarder exits after a hard-coded 5-second jobReady wait, which can silently break new tunnels when the agent/session is slow to acknowledge job.accepted (e.g., loaded VM or transient network delay). In that case the reader goroutine may stay alive, but no local bytes are ever forwarded, so the connection appears hung with no actionable error. This should wait on context/connection lifecycle (or surface an explicit timeout error) instead of unconditionally returning at 5 seconds.
Useful? React with 👍 / 👎.
| closeEv.Payload = map[string]interface{}{"reason": "host_eof"} | ||
| _ = send(closeEv) | ||
| return |
There was a problem hiding this comment.
Cancel tunnel job after local EOF to prevent leaked sessions
On local read termination, the code only sends stream.close and returns, but does not cancel the remote job. If the VM-side target keeps the socket open after receiving EOF (valid TCP half-close behavior), tun.connect can continue blocking on recv and the host-side handler can remain stuck waiting for terminal envelopes, leaking a long-lived session per connection. Sending a cancel (or otherwise forcing full remote teardown) on local close avoids this hang path.
Useful? React with 👍 / 👎.
… tun -R stub (#4) Closes the remaining deferred items from MASTER.md §10. Each command is contained and useful in v0 without further user input. * TOFU SSH host-key (internal/sshlife/ssh.go + tofu_test.go): Dial() now defaults to TOFUHostKey(~/.xpc/known_hosts) -- writes <host> <key-type> <base64-key> on first contact, byte-matches on subsequent contacts, refuses on key change with a clear MITM warning. 4 unit tests cover the four edge cases. * xpc trace start/stop/pull (internal/cli/trace.go): Sysinternals procmon.exe wrapper. start /accepteula /quiet /minimized /backingfile <out> [/runtime N] via the same DETACHED_PROCESS spawn trick used by xpc bootstrap. stop calls procmon.exe /Terminate via a python subprocess (no cmd.exe quirks). pull is an alias for xpc cp. * xpc dbg run/analyze (internal/cli/dbg.go): One-shot cdb wrappers. run <target> [--command] auto-detects .dmp files (uses -z) and appends ;q so cdb exits cleanly. analyze is the shorthand for !analyze -v against a minidump. * xpc snap list/create/restore/delete (internal/cli/snap.go): Proxmox PVE HTTP API client at https://<host>:8006/api2/json/ with PVEAPIToken auth. Profile fields proxmox_host / proxmox_user are honored as defaults; the secret is expected via --proxmox-token or $XPC_PROXMOX_TOKEN. --proxmox-{host,user,token,node,vmid,insecure} flags layer on top. * xpc daemon start/stop/status/exec (internal/cli/daemon.go): Long-lived host-side process holding warm TLS+session connections per profile. IPC over ~/.xpc/run/daemon.sock with one-line JSON requests and stdout_b64/stderr_b64 chunked responses. Smoke verified end-to- end: 'xpc daemon exec ver' through the warm session prints 'Microsoft Windows XP [Version 5.1.2600]'. The CLI doesn't auto-route through it yet; opt-in for now. * xpc tun -R: stub. Returns a clear 'not yet implemented' error pointing at TASKS.md. Real reverse forwarding needs an agent->host tool.invoke primitive; tracked as deferred. CI fix: drop -coverprofile from go test (covdata tool missing on the setup-go@v5 runners) -- already merged via PR #3 commit 3d58226. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Builds on PR #2. Two new capability layers, both driven by the same
session-open + tool.invoke flow.
Phase 6c —
xpc tun -L localPort:vmHost:vmPortFirst subcommand to exercise both directions of ARCP streaming on a
single job.
agent/agent.py):tun.connecttool opens a VM-side TCP socket and pumps bytesto the host via
stream.chunk(delta_b64).Connection._dispatchnow routes client-sourcedstream.chunk/stream.closeenvelopes to the matching job'stun_socket. Non-tunjobs drop silently.
internal/cli/tun.go): reader goroutine + forwarder goroutinejobReadychannel so its firstchunk doesn't race ahead of
job.accepted.docs/sessions/phase-6c-tun.md): xpctl JSON pinground-tripped through
xpc tun -L 19578:127.0.0.1:9578.Phase 6d —
xpc ghidra/xpc idalifecycleThin wrappers built on Phase 6c.
xpc ghidra start [--binary] [--port] [--repo]/xpc ghidra stop-- detached-spawnghidraSvr.baton the VM via theos.dup2-to-NUL +DETACHED_PROCESStrick; PID atC:\xpc\ghidra.runlog.pid. Stop killsjava.exewhose command linematches
%ghidra%.xpc ida start [--binary] [--port]/xpc ida stop-- samepattern; default targets
win32_remote.exeon port 23946.xpc tun -L <port>:127.0.0.1:<port>to expose the server.Live verification of ghidra/ida waits until those tools are installed
on the VM; dry-runs verified locally.
Test plan
go test -race ./...greengolangci-lint runclean (0 issues)xpc tunxpctl-ping round-tripxpc ghidra start --dry-runandxpc ida start --dry-run🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Reliability
Documentation