AxiOwl Remote Execution Without Open Manager Ports

Remote execution usually creates a security question before it creates an automation question: what has to be listening on the remote machine? AxiOwl's current remote design keeps that answer deliberately narrow. The Windows-side AxiOwl runtime acts as the local coordinator and remote provider, while the Linux-side package acts as a remote Codex endpoint that is reached through SSH and a stdio relay command.

That means the remote path does not require opening a public AxiOwl manager port on the Linux node. The initiating machine uses normal SSH access, starts a remote axiowl relay-session --stdio process, passes JSONL frames over standard input, and reads structured results from standard output.

The Remote Boundary

The AxiOwl activation README describes the runtime split directly:

That split is important. The Windows side owns the local registry and decides which remote node and remote agent should receive the message. The Linux side is intentionally smaller. It receives relay frames and delivers to the remote Codex CLI path. It does not try to become a general desktop automation service, remote GUI controller, tray app, VS Code bridge, Antigravity bridge, or relay chain.

The command surface on the Linux side is also small:

axiowl relay-session --stdio

That is the core of the "no open manager ports" model. The remote process is launched for the session through SSH. AxiOwl does not need a long-running TCP manager service exposed to the network for this delivery path.

How AxiOwl Addresses a Remote Node

AxiOwl stores remote machine connection details in its node registry. The CLI exposes node commands such as:

axiowl node add --id <node-id> --host <host> --ssh-user <ssh-user> [--key <ssh-key-path>] [--name <display-name>] [--alias <alias>]
axiowl node verify --id <node-id>

Verification is not just a label check. The implementation builds an SSH command with batch mode and a connection timeout, then asks the remote host to report basic identity with:

hostname; whoami; pwd; hostname -I

If SSH succeeds, AxiOwl updates the node row with a fresh verification timestamp. If it fails, the node row keeps the failure detail. That gives operators a concrete way to separate "this node is enrolled" from "this node can actually be reached right now."

Remote agents are represented separately from remote nodes. The app README shows the current pattern:

axiowl registry add-node --id "front" --host "<host>" --user "<ssh-user>" --key "<ssh-key-path>"
axiowl registry add-agent --name "Remote Test" --provider remote --node "front" --session "<remote agent name>"
axiowl send --to "Remote Test" --body "hello"

The remote target row has to identify both the node and the remote-side target. In the implementation, missing node IDs, missing host/user fields, and missing remote target session data produce explicit failures instead of silent fallback behavior.

The Relay Session

The remote provider implementation builds a temporary JSONL input file for the relay. The first frame is a protocol hello. The second frame is a delivery frame containing fields such as:

Then AxiOwl runs an SSH command shaped around the remote relay:

ssh -o BatchMode=yes -o ConnectTimeout=10 <user>@<host> "axiowl relay-session --stdio"

The local side feeds the JSONL file into the remote process over stdin and captures stdout. The remote relay writes structured response frames back, including deliver_result for the main delivery outcome and error for protocol or delivery failures.

Inside remote_relay.cpp, the relay accepts hello and deliver frames. For a delivery frame, it validates that the target and body are present, sets up a live upstream outbox path, and then either:

The remote side then emits a deliver_result frame with the request ID, message ID, state, target, provider, error text, and detail string. The local side parses that output and records evidence such as the remote request ID, remote message ID, remote provider, and remote detail.

Why Stdio Matters

The stdio relay is a practical security boundary. It makes remote execution depend on SSH policy that operators already know how to manage: keys, allowed users, host firewalls, audit logs, bastion patterns, and private network routes.

There is no need to publish an AxiOwl manager HTTP API on the remote host for this path. There is no extra inbound service port for a scanner to find. The remote process exists because SSH started it, handles the frames for that session, and exits when the stream ends.

This also keeps failure modes simple. If SSH cannot connect, AxiOwl reports an SSH relay failure. If the remote AxiOwl binary is not installed, the remote command fails. If the relay returns no deliver_result, the local side reports that directly. These are operationally plain failures, not hidden retries through unknown transport paths.

Remote Discovery Uses the Same Direction

Remote discovery follows the same outbound SSH posture. The discovery_remote.cpp implementation runs a remote command like:

axiowl discover codex --json --target <target>

over SSH. It reads JSON lines from the remote side, looks for discovered_agent records, and converts verified matches into local provider=remote rows. A discovered remote Codex target is stored with a provider session value that combines the remote provider and remote provider session. That lets the local registry know both "this is a remote row" and "this is the concrete provider/session on the far side."

The developer docs are explicit that discovery should not silently turn stale data into sendable targets. AxiOwl discovery may refresh, enrich, downgrade, or repair a target once during send, but it should not hide provider delivery failures or make stale display names stronger than provider-owned session IDs.

Installer Support

The Windows installer line includes remote-node steps for enrollment, deployment, and discovery. The activation README says the MSI can enroll configured remote nodes, deploy the packaged Linux payload over SSH/SCP, run the remote Linux installer, and then run remote discovery. That work is performed by the native installer helper.

The Linux remote installer script installs /usr/local/bin/axiowl, installs the bundled Codex plugin payload under the user's AxiOwl/Codex data area, writes a local Codex plugin marketplace file, runs Codex plugin registration, and verifies that the MCP server exposes axiowl_send_message.

The script also checks expected prerequisites and version identity. For example, it requires sudo, install, and codex, installs the binary, runs axiowl --version, and expects the current version string to match axiowl 0.1.0.

The repository's packaged remote-nodes.json is currently an empty array, which means the installer supports the flow but does not ship a hardcoded remote node inventory. Operators must provide the actual remote node configuration through the intended local configuration path or existing node registry.

What This Does Not Mean

"Remote execution without open manager ports" does not mean "anything can relay anywhere." The current Linux remote app is intentionally limited to Codex CLI delivery. The README says it does not support VS Code, Antigravity, desktop IPC, GUI behavior, tray behavior, or remote relay chaining.

The source also contains guardrails for unsupported remote paths. On Linux remote builds, provider modules for desktop-oriented providers return explicit unsupported results with evidence stating that Linux remote AxiOwl intentionally supports Codex CLI delivery only. In the current local-provider remediation dispatch code, broad provider=remote delivery is also gated as out of scope for that build path, while the dedicated remote relay implementation and installer/deployment pieces remain present in the codebase.

That distinction matters for operators and developers: the transport pattern is concrete and implemented in the remote relay code, but remote provider availability depends on the build path and dispatch wiring being used.

Operator Value

The value of this model is operational restraint. AxiOwl can reach a Linux node through SSH, deliver a structured message to a remote Codex endpoint, and record evidence without requiring teams to expose a new manager service.

For an operator, that means remote execution can fit into existing host access rules. For a developer, it means the remote protocol is inspectable: JSONL frames in, JSONL frames out, explicit result states, and source files that show where the node registry, discovery command, relay command, and delivery result are handled.

The practical workflow is:

  1. Install or deploy the Linux remote AxiOwl payload.
  2. Register the remote node with host, SSH user, and optional key path.
  3. Verify the node over SSH.
  4. Discover or register the remote target.
  5. Send through the local AxiOwl registry to the remote target.
  6. Inspect the evidence when delivery fails or succeeds.

Closing

AxiOwl's remote execution design is not built around opening a manager port and hoping it is protected well enough. It is built around the local coordinator initiating SSH, launching a small remote stdio relay, and keeping the Linux remote role focused on Codex CLI delivery.

That makes the current remote model easier to reason about: SSH is the network boundary, stdio is the relay boundary, the registry is the addressing boundary, and explicit provider support is the capability boundary.