Skip to content

fix(ssh-config): strip quotes and handle spaced = separator - #8

Merged
enwaiax merged 1 commit into
mainfrom
fix/ssh-config-quoted-values
Aug 22, 2026
Merged

enwaiax merged 1 commit into
mainfrom
fix/ssh-config-quoted-values

Conversation

@enwaiax

@enwaiax enwaiax commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Closes #7.

Problem

src/shuttle/core/ssh_config.py split each config line with line.split(None, 1), falling back to line.split("=", 1). Two bugs fall out of that:

1. Quotes are not stripped (the reported issue)

IdentityFile "~/.ssh/id_rsa" parsed to the literal string '"~/.ssh/id_rsa"'. SSHConfigEntry.resolve_key() then looked for a path containing quote characters, found nothing, and silently fell back to DEFAULT_KEY_PATHS — so shuttle node import listed the host as key-less with no error explaining why. Single quotes behave the same way.

2. A spaced = separator leaks the = into the value (found while reproducing)

Port = 2222 matched the whitespace split first, giving the value '= 2222'. _build_entry then does int('= 2222') → ValueError, which propagates out of parse_ssh_config() and aborts the whole file — no hosts at all can be imported. The existing test_equals_separator only covered the unspaced Port=3022, so this was never exercised. Key = Value is valid ssh_config(5) syntax.

Fix

Replace the ad-hoc splitting with a single line regex plus an unquote helper:

_LINE_RE = re.compile(r"^(\S+?)(?:\s*=\s*|\s+)(.*)$")
  • The separator alternation accepts whitespace or an optionally-spaced =.
  • The value captures the rest of the line, so quoted paths containing spaces stay intact.
  • _unquote() strips exactly one pair of matching outer quotes (" or '), matching ssh(1).
  • Values that are empty after unquoting are skipped, so a bare keyword or User "" no longer overwrites the default.

Note on the issue text

The issue suggests split(None, 1) "splits at the first whitespace inside the quoted value and drops the rest of the path". That part isn't quite right — maxsplit=1 keeps the remainder, so "~/.ssh/my key" was already preserved in full; the only reason it failed was the unstripped quotes. The fix and the test for it are unchanged either way.

Tests

Added to tests/test_core/test_ssh_config.py, written first and confirmed failing against the old parser:

Test Old behavior
test_equals_separator_with_spaces ValueError: invalid literal for int(): '= 3022'
test_double_quotes_stripped '"10.0.0.1"'
test_single_quotes_stripped "'10.0.0.1'"
test_quoted_path_with_spaces_preserved '"~/.ssh/my key"'
test_quoted_equals_separator '= "~/.ssh/id_rsa"'
test_key_without_value_skipped user == '""'
test_resolve_quoted_key_from_config resolve_key() → None
test_resolve_quoted_key_with_spaces_from_config resolve_key() → None

test_inner_quotes_preserved is a guard that _unquote strips only the outer pair.

Verification

  • uv run pytest -q — 220 passed (23 in test_ssh_config.py, up from 14)
  • uv run ruff check src/shuttle/ tests/ — clean
  • uv run ruff format — clean

`IdentityFile "~/.ssh/id_rsa"` kept its surrounding quotes, so
`resolve_key()` looked for a literal path containing `"` characters,
found nothing, and silently fell back to the default key paths — the
host was reported as key-less by `shuttle node import`.

The same `split(None, 1)` logic also mishandled a spaced `=`
separator: `Port = 2222` parsed as the value `"= 2222"`, and
`_build_entry` then raised `ValueError` from `int()`, aborting the
whole parse so no hosts could be imported at all.

Replace the ad-hoc splitting with a line regex that accepts either
whitespace or an optionally-spaced `=` and captures the rest of the
line, then strip a single pair of matching outer quotes. Values that
are empty after unquoting are skipped.

Closes #7
@enwaiax
enwaiax merged commit 75c98fe into main Aug 22, 2026
4 checks passed
@enwaiax
enwaiax deleted the fix/ssh-config-quoted-values branch August 22, 2026 08:19
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.

SSH config parser does not strip quotes from IdentityFile values

1 participant