can1357/oh-my-pi · error

Invalid SSH host "${host}": an SSH destination must not begi

Error message

Invalid SSH host "${host}": an SSH destination must not begin with "-" (argument-injection guard)

What it means

buildSshTarget is the single chokepoint that renders every SSH destination string used by connections, transfers, and sshfs mounts. SSH parses a destination argument starting with "-" as an option flag, so a hostile or corrupted host value like `-oProxyCommand=...` would be executed locally as an ssh option instead of treated as a hostname. The library throws this error to guarantee no user-controlled string can be injected into the ssh argv as an option.

Source

Thrown at packages/coding-agent/src/ssh/utils.ts:12

export function sanitizeHostName(name: string): string {
	const sanitized = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
	return sanitized.length > 0 ? sanitized : "remote";
}

export function buildSshTarget(username: string | undefined, host: string): string {
	// SSH treats a destination starting with "-" as an option, so a host/user of
	// `-oProxyCommand=...` becomes local command execution. Reject before this
	// string reaches any `ssh` argv (this is the single render chokepoint for
	// every connection, transfer, and sshfs mount).
	if (host.startsWith("-")) {
		throw new Error(
			`Invalid SSH host "${host}": an SSH destination must not begin with "-" (argument-injection guard)`,
		);
	}
	if (username?.startsWith("-")) {
		throw new Error(
			`Invalid SSH username "${username}": an SSH username must not begin with "-" (argument-injection guard)`,
		);
	}
	return username ? `${username}@${host}` : host;
}

/**
 * Single-quote a path for a POSIX remote shell, escaping embedded single quotes.
 * Mirrors the private `quoteRemotePath` in `tools/ssh.ts`; shared here for the
 * `ssh://` file-transfer helpers.
 */
export function quotePosixPath(value: string): string {
	if (value.length === 0) return "''";

View on GitHub (pinned to 9690622007)

Solutions

  1. Fix the host value in your ssh:// URL or configuration so it is a real hostname (no leading dash).
  2. If you need ssh options (ProxyJump, port, etc.), pass them through the dedicated option fields or ~/.ssh/config, not inside the host string.
  3. Validate/sanitize the host before it reaches buildSshTarget, e.g. strip a scheme and reject leading dashes.

Example fix

// before
buildSshTarget(undefined, "-oProxyCommand=evil")
// after
buildSshTarget(undefined, "jump.example.com") // put ProxyCommand in ~/.ssh/config
Defensive patterns

Strategy: validation

Validate before calling

function safeSshHost(host: string): boolean { return host.length > 0 && !host.startsWith("-"); }
if (!safeSshHost(host)) throw new Error("host must not begin with '-'");

Try / catch

try { const target = buildSshTarget(user, host); } catch (err) { log.warn("ssh target rejected", { host, err }); return; }

Prevention

When it happens

Trigger: Calling buildSshTarget (directly or via buildRemoteCommand/target) with a host string whose first character is "-", e.g. from a malformed ssh:// URL, a config value like `-oProxyCommand=evil`, or a parsed destination where the option separator was lost.

Common situations: A mistyped ssh:// URL such as ssh://-oProxyCommand=x@host, a config file with the host field holding ssh options instead of a hostname, or an attacker-controlled remote/repository URL attempting argument injection against tooling that shells out to ssh.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/f3ada7c5b334b8c5. Report an issue: GitHub.