odysseus-dev/odysseus · warning · HTTPException

Invalid remote_host — must be host or user@host, no SSH opti

Error message

Invalid remote_host — must be host or user@host, no SSH option syntax

What it means

A FastAPI HTTPException(400) from validate_remote_host: the remote_host value failed the strict regex that permits only 'host' or 'user@host' forms (letters, digits, dots, underscores, hyphens). Anything carrying SSH option syntax — '-p 22', '-i key', 'ssh://...' — is rejected to prevent option injection into downstream ssh commands.

Source

Thrown at routes/_validators.py:16

import re

from fastapi import HTTPException


_REMOTE_HOST_RE = re.compile(
    r"^(?:[A-Za-z0-9][A-Za-z0-9._-]*@)?[A-Za-z0-9][A-Za-z0-9._-]*$"
)
_SSH_PORT_RE = re.compile(r"^\d{1,5}$")


def validate_remote_host(v: str | None) -> str | None:
    if v is None or v == "":
        return None
    if not _REMOTE_HOST_RE.match(v):
        raise HTTPException(
            400,
            "Invalid remote_host — must be host or user@host, no SSH option syntax",
        )
    return v


def validate_ssh_port(v: str | None) -> str | None:
    if v is None or v == "":
        return None
    if not _SSH_PORT_RE.fullmatch(str(v)):
        raise HTTPException(400, "Invalid ssh_port")
    port = int(v)
    if port < 1 or port > 65535:
        raise HTTPException(400, "Invalid ssh_port")
    return str(port)

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Send only 'user@host' or 'host'; move the port into the separate ssh_port field
  2. Remove ssh:// scheme prefixes and option flags
  3. For IPv6, file a feature request / use a hostname, since the regex rejects colons and brackets

Example fix

# before
remote_host="user@host -p 2222"
# after
remote_host="user@host", ssh_port=2222
Defensive patterns

Strategy: validation

Validate before calling

import re
REMOTE = re.compile(r'^(?:[A-Za-z0-9][A-Za-z0-9._-]*@)?[A-Za-z0-9][A-Za-z0-9._-]*$')
if not REMOTE.match(remote_host or ''):
    remote_host = remote_host.split()[-1].lstrip('ssh://')  # or reject client-side

Type guard

def is_valid_remote_host(v: str | None) -> bool:
    return v is None or bool(re.match(r'^(?:[A-Za-z0-9][A-Za-z0-9._-]*@)?[A-Za-z0-9][A-Za-z0-9._-]*$', v))

Try / catch

try:
    resp = client.post('/api/remote', data={'remote_host': h})
except HTTPError as e:
    if e.response.status_code == 400 and 'remote_host' in e.response.text:
        h = extract_bare_host(h); retry()
    raise

Prevention

When it happens

Trigger: POSTing a remote-host form/API payload with remote_host like 'ssh -p 2222 host', 'user@host -i ~/.ssh/id_ed25519', 'ssh://user@host:22', or an IPv6 literal with brackets/colons.

Common situations: Users pasting a full ssh command line into a host field; URLs instead of bare hostnames; bracketed IPv6 addresses which the character class does not allow.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/a23bf85a9596cad6. Report an issue: GitHub.