can1357/oh-my-pi · error

Invalid SSH username "${username}": an SSH username must not

Error message

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

What it means

Companion to the host guard in buildSshTarget: SSH also parses `user@host` destinations where a username starting with "-" would let an option like `-oProxyCommand=...` slip through before the @ separator in some parse paths. The library rejects any username beginning with "-" before the target string reaches an ssh argv.

Source

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

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 "''";
	return `'${value.replace(/'/g, "'\\''")}'`;
}

/**
 * Wrap a POSIX command in `<shell> -c '<command>'` so it runs under the

View on GitHub (pinned to 9690622007)

Solutions

  1. Correct the username in the ssh:// URL or config so it contains no leading dash.
  2. Pass ssh options via ~/.ssh/config or the tool's option fields instead of the username.
  3. Validate the username before calling buildSshTarget.

Example fix

// before
buildSshTarget("-oProxyCommand=evil", "host.example.com")
// after
buildSshTarget("deploy", "host.example.com")
Defensive patterns

Strategy: validation

Validate before calling

function safeSshUser(user: string): boolean { return !user.startsWith("-"); }
if (username && !safeSshUser(username)) throw new Error("username must not begin with '-'");

Try / catch

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

Prevention

When it happens

Trigger: Calling buildSshTarget with a username string starting with "-", e.g. from a URL like ssh://-oProxyCommand=evil@host or a config where the user field contains ssh options.

Common situations: Malformed ssh:// URLs where the userinfo component holds options, attacker-crafted remote URLs attempting option injection, or copy-paste errors putting flags into the username field.

Related errors


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