Skip to content

Pass sshv2 arguments through to ssh - #328

Merged
dbmikus merged 3 commits into
mainfrom
dylan/sshv2-passthrough
Aug 11, 2026
Merged

Pass sshv2 arguments through to ssh#328
dbmikus merged 3 commits into
mainfrom
dylan/sshv2-passthrough

Conversation

@dbmikus

@dbmikus dbmikus commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

amika sandbox sshv2 now mirrors the underlying ssh command: every argument written after sshv2 is handed to the system ssh binary unchanged, and the sandbox name takes ssh's destination slot.

Previously those arguments were parsed by Cobra, which rejected ssh's own options before ssh ever saw them. Asking for a port forward failed outright:

$ amika sandbox sshv2 my-box -L 6789:localhost:3010
unknown shorthand flag: 'L' in -L

The only way through was to smuggle the options past Cobra behind a --, which is neither discoverable nor documented. They now work directly:

# Forward local port 6789 to port 3010 inside the sandbox, without a shell
amika sandbox sshv2 -N -L 6789:localhost:3010 my-sandbox

# SOCKS proxy on local port 1080
amika sandbox sshv2 -N -D 1080 my-sandbox

# Run a command instead of opening a shell
amika sandbox sshv2 my-sandbox uptime

Because arguments after the subcommand belong to ssh, amika's own flags go before it:

amika sandbox --remote sshv2 -N -L 8080:localhost:80 my-sandbox

Cobra strips the command path before a command sees its arguments, so both placements of a flag arrive identically. internal/cliargs recovers the boundary from the process argv, which is what makes the rule positional.

--help is the exception and still prints amika's help, as does amika help sandbox sshv2. The command defines no flags of its own, so ssh's -t reaches ssh directly rather than being shadowed by a Cobra flag of the same name.

Local (-L) and dynamic (-D) forwarding are supported by the sandbox's SSH server. Remote forwarding (-R), agent forwarding (-A), and X11 forwarding are refused server-side.

amika scpv2 already forwarded its arguments to scp and is unchanged.

Compatibility

Existing invocations keep working, including the -- form: ssh consumes the marker itself, so amika sandbox sshv2 my-box -- uptime still runs uptime.

Verification

make ci
go test ./internal/cliargs ./internal/ssh ./cmd/amika/...

Tests assert the exact argv the command hands to ssh, driving the real Cobra command through a seam over the exec, and cover port forwards, -o, remote commands, flag placement on both sides of the subcommand, a missing sandbox name, and all three help paths.

ssh's own argument grammar was confirmed against a local sshd echoing $SSH_ORIGINAL_COMMAND: options are accepted before and after the destination, the first non-option token after the destination begins the remote command, and -- is consumed by ssh rather than forwarded.

make ci fails only in internal/amikad, internal/amikad/sshd, and internal/amikad/state, which fail identically on a clean checkout of main on macOS ("amikad sensitive writes require Linux", "sensitive file path contains a symlink").

Cobra removes the command path from an argv before handing it to a
command, so `amika --output json sandbox sshv2 box` and `amika sandbox
sshv2 --output json box` arrive identically as `[--output json box]`.
Telling the two apart requires the original process argv, where the
subcommand token still marks the boundary between the arguments amika
interprets and the arguments it forwards to an underlying utility.

`cliargs` recovers that boundary, and adds the getopt helpers needed to
find an operand in an argv bound for ssh or scp so that a value such as
the `6789:localhost:3010` in `-L 6789:localhost:3010 box` is not
mistaken for the destination.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2224480bfe

ℹ️ 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".

// separate token (from ssh(1)). Locating an operand requires them: without
// them the "6789:localhost:3010" in "-L 6789:localhost:3010 my-box" would be
// mistaken for the destination.
const SSHArgLetters = "BbcDEeFIiJLlmOopQRSWw"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize -P as an SSH option that consumes a value

When users pass OpenSSH's -P tag option, FirstOperand treats the tag as the sandbox name because P is absent from this set. For example, amika sandbox sshv2 -P deployment my-box queries the API for sandbox deployment instead of my-box and never invokes SSH. I confirmed against OpenSSH 9.6's ssh -h usage, which lists [-P tag]; include P among the value-taking option letters.

Useful? React with 👍 / 👎.

Comment on lines +119 to +121
for _, a := range args {
if len(a) == 0 || a[0] != '-' || a == "--" || a == "-" {
return false // first operand or end-of-options marker

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip SSH option values when detecting a help flag

When --help follows any option with a separate non-dash value, this loop mistakes that value for the destination and stops before reaching the help flag. Thus amika sandbox sshv2 -L 6789:localhost:3010 --help returns “missing sandbox name” instead of the command help, even though -L's value is not an operand. Account for value-taking SSH options while scanning the leading option region.

Useful? React with 👍 / 👎.

`amika sandbox sshv2` parsed its arguments with Cobra, which rejected
ssh's own options before ssh ever saw them. Asking for a port forward
with `sshv2 my-box -L 6789:localhost:3010` failed outright with
`unknown shorthand flag: 'L' in -L`. The only way through was to smuggle
the options past Cobra behind a `--`, which is neither discoverable nor
documented.

Arguments written after `sshv2` are now handed to the system ssh binary
unchanged, and the sandbox name takes ssh's destination slot, so ssh's
options work as they do with ssh itself. Amika's own flags go before the
subcommand, which is where the argv split attributes them. `--help` is
the exception and still prints amika's help, as does
`amika help sandbox sshv2`.

The `-t` flag is dropped, since ssh's own `-t` now reaches ssh directly
rather than being shadowed by a Cobra flag of the same name.

Existing invocations keep working, including the `--` form: ssh consumes
the marker itself, so `sshv2 my-box -- uptime` still runs `uptime`.

`amika scpv2` already forwarded its arguments to scp and is unchanged.
The help block explained how arguments reach ssh rather than how to
use the command. Describe the grammar and the flag placement a caller
needs, and drop the walkthrough of which token goes where.
@dbmikus
dbmikus force-pushed the dylan/sshv2-passthrough branch from 2387ad5 to ba326a0 Compare August 11, 2026 03:47
@dbmikus
dbmikus merged commit b99b4cf into main Aug 11, 2026
1 check passed
@dbmikus
dbmikus deleted the dylan/sshv2-passthrough branch August 11, 2026 03:49
dbmikus added a commit that referenced this pull request Aug 12, 2026
Release amika@v0.14.1.

Changes since amika@v0.14.0:
- Use sshv2 for the snapshot scrub e2e exec steps (3844863)
- Pass sshv2 arguments through to ssh (#328) (b99b4cf)

Release-Component: amika
Release-Version: v0.14.1
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