Skip to content

Share SSH connections and stop retry loops on blocked hosts - #9

Merged
wintermeyer merged 2 commits into
wintermeyer:mainfrom
jpawlowski:feat/ssh-connection-sharing
Sep 17, 2026
Merged

wintermeyer merged 2 commits into
wintermeyer:mainfrom
jpawlowski:feat/ssh-connection-sharing

Conversation

@jpawlowski

Copy link
Copy Markdown
Contributor

Symptom: A session opens many short SSH connections to one host. sshd MaxStartups and PerSourcePenalties, fail2ban and IPS rules count connections, not commands, so heinzel can lock itself out, and a retry loop keeps the block alive.

Change: The mandatory SSH options in CLAUDE.md add connection sharing, socket under ~/.cache/heinzel (under .ssh/ the taboo guard blocks every remote file operation). A separate fresh-login option set serves access tests, including the root probe. rules/ssh-connections.md covers bundling and sharing, rules/ssh-unreachable.md retries and telling a blocked path from a broken host. The fleet audit uses the standard options.

Verified: Ten calls through one master opened no new TCP connection, ~120 ms per call instead of ~800 ms. Both socket paths tested against guard-taboos.sh; ssh -G confirms a repeated -o keeps its first value.

An AI agent wrote this text in my name. I know that is problematic.

Rate limits, sshd PerSourcePenalties and IPS rules count TCP
connections, not commands, so a busy session can lock itself out and
a retry loop keeps the block alive.

- CLAUDE.md: the mandatory SSH options turn on OpenSSH connection
  sharing, with the socket under ~/.cache/heinzel (a path under .ssh/
  makes the taboo guard treat every remote file operation as a key
  operation). A separate fresh-login option set covers access tests.
- rules/ssh-connections.md: bundle calls, share connections, use
  fresh logins for access tests, never close a master you did not
  start.
- rules/ssh-unreachable.md: no retry loops, tell a blocked path from
  a broken host, probe IPv4 and IPv6 separately.
- rules/privilege-escalation.md: the root probe uses fresh-login
  options.
- heinzel-fleet-audit: probes use the standard options.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jpawlowski

Copy link
Copy Markdown
Contributor Author

@wintermeyer since CODEOWNERS is missing, I think you miss a notification so I'll just manually ping you here.

@wintermeyer

Copy link
Copy Markdown
Owner

@wintermeyer since CODEOWNERS is missing, I think you miss a notification so I'll just manually ping you here.

Thanks for the ping. I haven't tackled this yet because I ran out of tokens. What a world we are living in! 😉
It is on my to-do-list for tomorrow when the next reset happens.

Without ~/.cache/heinzel every shared SSH call exits 255 after a successful login, which costs a connection and reads as a broken host, so a SessionStart hook now creates it. fail2ban, sshguard and PerSourcePenalties count failed or aborted logins rather than connections, and OpenSSH moves to the other address family only after ConnectTimeout, so the rules now say that and replace the nc probes, which those tools count, with -v on the one allowed retry. The rule text is cut to what the agent acts on, and the guard lists the .ssh ControlPath over-match as a known false positive instead of a test pinning it.

An AI agent wrote this text in my name. I know that is problematic.
@wintermeyer
wintermeyer merged commit a15047f into wintermeyer:main Sep 17, 2026
1 check passed
@wintermeyer

Copy link
Copy Markdown
Owner

Thanks, Julian, this is a really useful change. I pushed one commit on top before merging: a SessionStart hook now creates ~/.cache/heinzel, because without it every call exits 255 right after the login. fail2ban and PerSourcePenalties only count failed or aborted logins, so the nc probes are gone (fail2ban's aggressive mode counts them) and the one retry uses -v instead. I also trimmed the rule text.

An AI agent wrote this text in my name. I know that is problematic.

wintermeyer added a commit that referenced this pull request Sep 17, 2026
Four changes since 2.19.0. Heinzel now shares one SSH connection per host and stops retrying when SSH does not answer, both from Julian Pawlowski's #9; it avoids the failed logins fail2ban counts; and the macOS rules note that brew upgrade leaves the old service process running.

Release notes in CHANGELOG.md; the reasoning per change is in the preceding commits.

An AI agent wrote this text in my name. I know that is problematic.
@jpawlowski
jpawlowski deleted the feat/ssh-connection-sharing branch September 17, 2026 10:06
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.

2 participants