Skip to content

fix(ssh): make --json actually emit JSON for remote scans - #64

Merged
Mapika merged 1 commit into
mainfrom
fix/ssh-json
Jul 31, 2026
Merged

Mapika merged 1 commit into
mainfrom
fix/ssh-json

Conversation

@Mapika

@Mapika Mapika commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Found while testing #63. portview ssh <host> --json has never worked, and it fails differently depending on the far end.

With portview installed remotely — fatal

--json is also the transport this side parses back, so it is injected into the remote command. The user's own flag was then appended to it:

$ portview ssh devbox --json
SSH error (devbox): error: the argument '--json' cannot be used multiple times

The remote clap rejects the repeat and the whole command dies. This is the common case — anyone scripting against a host that has portview installed.

Without portview remotely — silently ignored

Agentless collection succeeded and then rendered a table anyway, because run_ssh_scan unconditionally called display_port_table and never looked at the flag.

The fix

The injected flag and a user-typed one are indistinguishable once they are in the same list, so intent is captured first and the duplicate filtered out:

fn build_scan_args(remote_args: &[String]) -> (Vec<String>, bool) {
    let want_json = remote_args.iter().any(|a| a == "--json");
    let mut args = vec!["--json".to_string()];
    args.extend(remote_args.iter().filter(|a| *a != "--json").cloned());
    (args, want_json)
}

An empty result is now [] rather than No ports found on remote host. — this output gets piped, so prose on stdout is a parse error waiting to happen.

Verification

Both remote modes, against the same host, with the ss/portview availability on the far end varied deliberately:

== agentless --json (portview hidden on the far end) ==
[{"port":3000,"protocol":"TCP","pid":8,...,"local_addr":"127.0.0.1","start_time_unix":1785535092}]

== normal remote --json (portview present) ==
[{"port":3000,"protocol":"TCP","pid":8,...,"local_addr":"127.0.0.1","start_time_unix":1785535091}]

== no --json still renders a table ==
╭──────┬───────┬─────┬────────────── …

== empty result ==
[]

Byte-identical between the two paths apart from the timestamp. Three regression tests cover the arg construction, including the --json --json case that was fatal.

172 tests, fmt/clippy clean, macOS + Windows type-check.

With portview installed on the far end the flag was fatal: --json is also
the transport this side parses back, so the user's flag was appended to the
injected one and the remote clap rejected it twice. Without portview there,
agentless collection ignored the flag and printed a table.

Both paths now emit JSON, and an empty result is [] rather than prose.
@Mapika
Mapika merged commit 5ccb581 into main Jul 31, 2026
6 checks passed
@Mapika
Mapika deleted the fix/ssh-json branch July 31, 2026 22:10
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