macOS Security SSH Hardening Red Team

The Hidden SSH Port macOS Doesn't Tell You About

Why disabling Remote Login doesn't close all your SSH ports — and what we found when we audited ours
AIMF Security Blog • May 2026 • 11 min read
Networking diagram showing two independent SSH paths on a Mac

The hidden port: macOS Remote Login controls port 22, but a custom LaunchDaemon on port 2222 operates completely independently.

The Assumption Everyone Makes

If you disable Remote Login in macOS System Settings, SSH is off. That is the reasonable assumption. Apple's UI presents a single toggle, and when it is off, port 22 closes. Most administrators stop there.

We almost did too.

During a red team audit of our own infrastructure, we discovered something that should concern anyone running custom SSH services on macOS: a LaunchDaemon-based sshd on port 2222 continues to accept connections even when Remote Login is completely disabled. macOS has no awareness of it. There is no indicator in System Settings. No warning in the sharing panel. The port stays open, silently.

The Core Finding: macOS Remote Login controls only the system com.openssh.sshd on port 22. Any custom LaunchDaemon running sshd on another port operates with complete independence. Disabling one has zero effect on the other.
2
Independent sshd listeners
7
Security gaps found in audit
7
Defense layers deployed
0
macOS warnings shown

Why Custom SSH Ports Are Invisible to macOS

macOS manages SSH through a LaunchDaemon called com.openssh.sshd. When you toggle Remote Login on or off, macOS loads or unloads that specific daemon. It listens on port 22 and is the only SSH service the operating system knows about.

But macOS has no registry of "all SSH services." Any process can open a TCP listener on any port. If you install a second LaunchDaemon that runs sshd on port 2222 — as we did for our hardware-authenticated vault sync — macOS treats it like any other background service. It has no concept that this is also an SSH server.

The Two Independent Paths

Path A: System SSH (Port 22)

Controlled by: System Settings → General → Sharing → Remote Login

LaunchDaemon: com.openssh.sshd

Behavior: Toggle ON = port 22 opens. Toggle OFF = port 22 closes.

Visibility: Full. macOS knows about this service.

Path B: Custom SSH (Port 2222)

Controlled by: Nothing in the macOS UI

LaunchDaemon: com.opendlp.gate-sshd (custom)

Behavior: Loaded at boot via launchctl. Persists regardless of Remote Login state.

Visibility: None. macOS has no awareness this port is open.

This is not a vulnerability in macOS. It is an architectural reality. But it creates a dangerous assumption gap: an administrator who disables Remote Login believing they have closed all SSH access is wrong.

The macOS Tahoe sshd-session Bug

While investigating port 2222, we discovered a second, more severe problem: SSH is fundamentally broken on macOS Tahoe.

OpenSSH 10.2, which ships with macOS Tahoe, splits the SSH server into two binaries. The listener (sshd) accepts connections and then hands each one off to a per-connection handler (sshd-session) via fork() + exec(). The problem is that the socket file descriptor is not properly inherited across the exec() boundary.

The Result: Every SSH connection — on any port, in any mode — fails with "Broken pipe" during the banner exchange. This is not specific to custom ports or OpenDLP. Standard SSH on port 22 with Remote Login enabled fails identically.
Technical flow diagram showing the sshd to sshd-session socket inheritance failure on macOS Tahoe

The broken handoff: sshd-session loses the socket file descriptor after exec(), killing every connection.

The Failure Chain

# What happens on every SSH connection attempt: Client connects to :2222 → sshd accepts TCP connection → sshd fork() + exec("/usr/libexec/sshd-session") → sshd-session: getpeername(fd) → "Invalid argument" # socket fd lost → sshd-session: setsockopt TCP_NODELAY → "Invalid argument" → sshd-session: setsockopt SO_KEEPALIVE → "Invalid argument" → sshd-session: BSM audit: getaddrinfo "UNKNOWN" → connection dies → client sees "Broken pipe"

Every Mode Fails

All sshd configurations tested — all broken

  • inetd mode (sshd-keygen-wrapper) — Broken pipe
  • Standalone daemon (sshd -D -p 2222) — Broken pipe
  • Standalone + no PAM (sshd -D -p 2222 -o UsePAM=no) — Broken pipe
  • Standard port 22 (system com.openssh.sshd) — Broken pipe

This is a system-wide macOS issue, not specific to custom configurations.

The Security Audit: 7 Gaps Found

With port 2222 operating outside macOS's awareness, we conducted a full security audit of the gate sshd configuration. The results were sobering — even with hardware YubiKey authentication in place, we found seven gaps that could weaken the overall security posture.

Concentric ring diagram showing seven defense-in-depth security layers protecting the OpenDLP gate SSH port

Defense in depth: seven layers between an attacker and the gate, each independently enforceable.

1. No Firewall Rule for Non-LAN Traffic

Gap: Murus (macOS PF firewall) had default-deny inbound, but no explicit block for non-LAN sources targeting port 2222.

Fix: Added PF rules — block drop in log quick proto tcp from ! 192.168.5.0/24 to any port 2222 — persisted in /etc/murus/murus.custom.

2. Gate User Had a Real Shell

Gap: The opendlp-gate user's shell was /bin/bash. If ForceCommand were ever bypassed (sshd bug, config error), an attacker would land in a real interactive shell.

Fix: Changed shell to /usr/bin/false. Even a ForceCommand bypass now yields nothing.

3. Keyboard-Interactive Authentication Left Default

Gap: While PasswordAuthentication was disabled, KbdInteractiveAuthentication was not explicitly set — leaving a potential authentication vector open.

Fix: Explicitly set KbdInteractiveAuthentication no.

4. No Session or Auth Attempt Limits

Gap: No MaxAuthTries or MaxSessions set — an attacker could attempt unlimited authentication cycles or open unlimited sessions.

Fix: Added MaxAuthTries 3 and MaxSessions 2.

5. Port Forwarding Not Fully Restricted

Gap: AllowTcpForwarding no was set, but PermitOpen and PermitListen were not — leaving potential tunnel abuse vectors.

Fix: Added PermitOpen none and PermitListen none.

6. Loopback IP in Device ACL

Gap: The YubiKey device ACL listed 127.0.0.1 as a known IP — meaning a loopback-source-spoofing attack could bypass the IP binding check.

Fix: Changed to the actual LAN IP (192.168.5.47).

7. User Environment Injection

Gap: PermitUserEnvironment was not explicitly disabled — theoretically allowing environment variable injection via ~/.ssh/environment.

Fix: Explicitly set PermitUserEnvironment no.

What Was Already Strong

The audit was not all gaps. Several layers of protection were already in place and validated during testing.

Existing Protections (Pre-Audit)

  • YubiKey challenge-response — Hardware PIV cryptographic signature required for every connection. No password or SSH key alone is sufficient.
  • ForceCommand (double-enforced) — Both the sshd Match block and authorized_keys command= directive restrict the connection to a single controlled entry point.
  • PubkeyAuthentication only — No password, no keyboard-interactive, no host-based auth.
  • E5 crypto hardening — AEAD ciphers only (ChaCha20-Poly1305, AES-256-GCM), post-quantum key exchange (sntrup761x25519), no SHA-1 MACs, no CBC mode.
  • Hidden system user — The gate user is not visible in the login window or standard user listings.
  • Append-only audit logschflags uappnd on all gate and verifier log files. Even root cannot delete entries without clearing the flag.
  • Nonce-based replay protection — Each authentication challenge uses a single-use nonce that is deleted immediately after verification.
Key Takeaway: The gaps we found were real, but they were defense-in-depth weaknesses — not primary attack vectors. An attacker would need to defeat YubiKey hardware authentication before any of these gaps became exploitable. The audit strengthened the outer perimeter around an already-strong core.

E5: Crypto Hardening Profile

As part of the audit, we deployed a restrictive cryptographic configuration for the gate sshd. This eliminates legacy algorithms that are technically supported by OpenSSH but offer no benefit on a modern network.

# /etc/ssh/sshd_config.d/01-opendlp-crypto.conf # OpenDLP gate crypto hardening (E5) Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com KexAlgorithms sntrup761x25519-sha512,curve25519-sha256,ecdh-sha2-nistp256

What This Eliminates

  • CBC ciphers — Vulnerable to padding oracle attacks
  • SHA-1 MACs — Collision attacks demonstrated since 2017
  • umac-64 — Insufficient MAC length for high-security contexts
  • Weak KEX — Removed diffie-hellman-group-exchange and group14 variants

The configuration validates cleanly with sshd -t and will take effect once the macOS Tahoe sshd-session bug is resolved.

Designing the Fix: A User-Facing Toggle

The root UX problem is clear: enabling or disabling the gate SSH listener requires terminal commands that no end user should need to run. If we are building security software that operates a hidden port, we have a responsibility to make that port controllable.

macOS app UI mockup showing the OpenDLP Remote Vault Sync toggle in both light and dark mode with Touch ID authentication

Proposed UX: a simple toggle with Touch ID authentication, auto-disable timer, and clear status indication.

Current State (Unacceptable)

# To enable the gate listener: sudo launchctl bootstrap system /Library/LaunchDaemons/com.opendlp.gate-sshd.plist # To disable it: sudo launchctl bootout system/com.opendlp.gate-sshd.plist

Proposed Design

Remote Vault Sync Toggle

  • Label: "Allow Remote Vault Sync" — not "SSH," because users don't need to know the protocol
  • Authentication: Touch ID or admin password required before enabling (this is a privileged operation)
  • Status indicator: Green dot = listening on port 2222, Gray = off
  • Port display: Shows "Port 2222 (LAN only)" when enabled
  • Auto-disable: Optional timer — "Turn off after 1h / 4h / 8h / Never" — prevents forgotten open ports
  • Independence: Does NOT require macOS Remote Login to be enabled
  • PF integration: Toggle also manages the PF firewall rule (LAN-only restriction)

Architecture

The toggle communicates with a privileged XPC helper running as root. The helper manages both the LaunchDaemon lifecycle (launchctl bootstrap/bootout) and the PF firewall anchor. This keeps the user-facing app in userspace while the privileged operations happen through a controlled, audited channel.

Design Principle: If your security software opens a network port, your security software must give users a clear, authenticated way to close it. Requiring terminal commands for basic on/off control is a UX failure.

Fix Options for the sshd-session Bug

The macOS Tahoe sshd-session bug blocks all SSH-based features. We evaluated four approaches:

Option A: Wait for Apple

Approach: Apple patches sshd-session socket inheritance in a future macOS update.

Pro: Zero effort. Con: Unknown timeline; blocks all SSH testing indefinitely.

Option B: Homebrew OpenSSH (Recommended)

Approach: Install via brew install openssh. Use /opt/homebrew/sbin/sshd, which ships a single binary without the sshd-session split.

Pro: Known working, decouples from Apple's build, version control. Con: Extra dependency, needs own host keys.

Option C: Dropbear

Approach: Lightweight SSH server with no sshd-session split.

Pro: Simple, no Apple dependencies. Con: Different config format, no ForceCommand support.

Option D: Enable Remote Login via GUI

Approach: System Settings → Sharing → Remote Login.

Pro: May fix sshd-keygen-wrapper path. Con: Did not work in testing — same broken pipe.

Current Live State

Here is exactly where things stand as of this writing. The security hardening is deployed and validated, but the transport layer is blocked by the macOS bug.

E5 crypto config deployed
F5 authorized_keys live
Gate sshd on :2222
A6 reaper & PF rule

What IS Running

  • ESF extension (v99) — All 15 AUTH + 9 NOTIFY subscriptions active, full vault protection
  • NE extension (v8) — DNS monitoring, DoH detection, exfil correlation
  • E5 crypto config — Deployed, validates with sshd -t
  • F5 authorized_keysrestrict,pty,command= enforced

What's Waiting on the sshd Fix

  • A6 session reaper — Script and plist ready, no sessions to reap
  • PF firewall rule — No port to protect until sshd works
  • E5 live verification — Can't test cipher negotiation without connections
  • UX toggle — Designed, awaiting working transport

What This Means for You

If you are running any custom SSH service on macOS — whether it is a development server, a remote management tool, or a security gate — you should assume that macOS has no awareness of it. The Remote Login toggle in System Settings controls only the system sshd on port 22. Everything else is invisible.

The most dangerous security assumption is the one that feels obvious. "I turned off SSH" feels definitive. On macOS, it might only be half true.

Action Items:

• Audit your LaunchDaemons for any custom sshd instances: sudo launchctl list | grep ssh
• Check for listeners on non-standard ports: sudo lsof -iTCP -sTCP:LISTEN -P | grep ssh
• If you find custom SSH ports, add explicit PF firewall rules to restrict source IPs
• Harden sshd configs: disable password auth, set session limits, restrict forwarding
• If on macOS Tahoe: test that SSH actually works — the sshd-session bug may affect you
• Consider Homebrew OpenSSH as a workaround until Apple patches the socket inheritance issue

We are publishing this because security findings have more value when shared. The port 2222 independence issue is not a macOS vulnerability — it is an architectural behavior. But it is one that creates a false sense of security, and that makes it worth documenting.

If you assume disabling Remote Login closes all SSH access on your Mac, verify that assumption today.