Repository navigation
Releases: gasvn/ssh2fa
Release list
SSH2FA 1.7.1 — reduce macOS UI CPU usage
Reduces unnecessary CPU work in the macOS interface during long-running sessions.
Changes
- Update the menu-bar icon and Dock badge only when their displayed state changes.
- Avoid reapplying unchanged window rendering properties after SwiftUI updates.
- Replace continuously animated connection indicators with static ringed indicators.
- Stop the menu-bar refresh task promptly when it is cancelled.
The previously reported sustained 100% CPU condition was not reproduced during investigation. These changes address unnecessary rendering work; they do not establish that every possible cause of high CPU has been eliminated.
Validation
- 310 macOS unit tests passed.
- Universal app and daemon: Apple Silicon and Intel.
- Stable code-signing identity and verified Ed25519 update manifest.
Download SSH2FA.dmg, or use Settings → About → Check for Updates. The accompanying SSH2FA.update.json authenticates in-app updates.
SSH2FA 1.7.0 — login-node tunnels, location gate, web interface
Tunnels can now point at a cluster's login node and move between login
nodes. Connections can be limited to places you trust, and there's a
web interface and a single command to run everything on Linux.
Tunnels to the login node
A service running on the login node itself — no SLURM job — can now be
tunnelled to. Open a tunnel's Node picker and choose Use login node.
An existing compute-node tunnel can switch to the login node and back; the
last compute node is remembered.
A tunnel already on one login node can move to another: pick the host in the
picker's new Cluster menu (TUI: h), then Use login node. A host
that isn't connected yet can still be chosen; the tunnel starts once it is.
CLI: ssh2fa login-node <tunnel> <host>.
Location gate
Settings → Location: only connect automatically from allowed countries,
regions, cities, network operators or IP ranges. Automatic connections from
anywhere else are paused. Connections that are already live stay up, and a
manual Connect anyway always works. Nothing is looked up while the gate
is off. CLI: ssh2fa location.
Web interface
ssh2fa web on serves hosts, credentials, tunnels (with the SLURM node
picker), location and logs over HTTPS with live updates. It runs as a
separate process that only allows an explicit list of actions, uses a
self-signed certificate whose fingerprint is printed, and is protected by a
256-bit token plus CSRF checks and per-address lockout.
One command on Linux
ssh2fa up | restart | down | doctor uses systemd when it's reachable and
starts the daemon directly when it isn't. restart always keeps SSH
connections alive. doctor points out common setup problems.
Fixes
- Direct tunnels respawned every minute. The forwarding process exited
right after starting, and the respawn then failed with "port forwarding
failed", looping forever. They now stay up, and stopping one actually
frees its port. - Starting the daemon with an empty host list no longer kills every live
SSH connection. - On Linux, a file vault is used when no keyring answers, instead of every
host failing.
SSH2FA 1.6.0 — Linux support, optional 2FA
SSH2FA now runs on Linux, and a host no longer needs 2FA to be added.
Accounts without 2FA
Leave the 2FA field empty when adding a host and it is registered as
password-only — it logs in normally, because the server never asks for a code.
Previously an empty secret was rejected as an invalid otpauth:// URL, so such
an account could not be added at all.
A secret can be added later, or removed, from Password & setup. A typo'd
secret is still refused at entry: blank means "no 2FA", it does not mean
"accept anything".
Linux
The daemon, CLI and TUI now run on Linux. The menu-bar app is macOS-only, so on
Linux the dashboard is ssh2fa (the TUI). Everything else is shared code — the
same warm ControlMasters, the same 2FA handling, the same tunnels and mounts.
git clone https://github.com/gasvn/ssh2fa && cd ssh2fa/auto2fa-rs
./scripts/install-linux.sh # desktop (keyring)
./scripts/install-linux.sh --vault file # headless serverNo root, nothing system-wide: binaries in ~/.local/bin plus a systemd user
service. Credentials go to the freedesktop Secret Service, or to an owner-only
0600 file on a headless box where no keyring exists. See
docs/LINUX.md.
The TUI can manage, not just drive
Press Enter on a host for its details: what is stored, the live 2FA code
(c), a real test login (t), and removal (D). Press e on a tunnel to
edit its ports, auto-start and name. Previously anything beyond connect/start
had to go through ssh2fa raw.
Fixes
- The daemon could be starved by launchd and never start. Its LaunchAgent
setProcessType=Background, putting it in a low-priority scheduling band; on
a loaded machine launchd created the process stub and never scheduled it far
enough to exec, leaving every host stuck on "Reconnecting". It presents as a
code-signing failure and is not one. - A broken pipe crashed the CLI.
ssh2fa … | headprinted a Rust panic
instead of exiting quietly like any other Unix tool. - Mounting could not work on Linux at all — the service's own hardening blocked
the setuidfusermount3that unprivileged FUSE mounts require. A failed mount
also now reports why instead of a bare failure. - Unmounting no longer requires
sshfsto be installed; you could previously be
left with a mount you had no way to remove.
Naming
The CLI and TUI are now ssh2fa and ssh2fa-tui, matching the app and the
daemon. ssh2fa with no arguments opens the dashboard.
SSH2FA v1.5.12
简体中文
不再出现莫名其妙的 macOS 密码提示
- 修复真正根因:开发测试二进制曾意外访问真实登录钥匙串,导致正式 daemon 后续读取凭据时仍可能被 macOS 要求授权。
- Cargo 测试和开发构建现在默认禁止访问真实 Keychain,避免再次污染用户凭据权限。
- 升级后会显示一次明确的“迁移已保存的登录”说明;只有用户主动开始迁移才会访问旧凭据。macOS 的确认是旧数据迁移,不是 SSH 登录。
- 新的 v4 凭据项由稳定签名、固定路径的正式 daemon 创建。迁移成功后,启动、断网重连和应用更新都不应反复要求 Mac 密码。
连接与更新
- 网络切换、睡眠唤醒和 SSH 短暂失败使用退避重试;同一 2FA 凭据的登录会串行化,避免并发连接重复消耗 TOTP。
- 已有 ControlMaster 在更新时保持运行;新 daemon 会接管,不强迫用户重新 SSH/2FA。
- 从 v1.5.12 开始,后续自动更新不再依赖付费 Apple Developer ID。应用会验证项目签发的 Ed25519 清单、版本、build、大小和 SHA-256 后才安装。
- 更新时先冻结旧 daemon,完成 app 替换后再启动新 daemon,避免移动中的程序触发 Keychain 权限异常。
验证
- 276 个 macOS 测试通过。
- 626 个 Rust workspace 测试通过,2 个环境依赖测试按设计跳过,0 失败。
- Universal Apple Silicon + Intel,build 163。
English
- Fixes the actual repeated-password-prompt root cause: a Rust test harness had touched the real login Keychain and left a test-binary ACL on saved credentials.
- Cargo test/development binaries are now blocked from the real Keychain by default.
- Credential access is gated behind one clearly explained, user-initiated migration into a fresh v4 item owned by the stable packaged daemon.
- Adds reconnect backoff, per-credential login serialization, safer network recovery, and preservation of live ControlMasters during updates.
- Starting with v1.5.12, future updates use a project-signed Ed25519 manifest and no longer require a paid Apple Developer membership.
Install / Update
Important bootstrap step: v1.5.11 and earlier do not contain SSH2FA's new Ed25519 update public key. Update to v1.5.12 manually once with Homebrew or the DMG below. After v1.5.12 is installed, future releases can use the secure in-app updater.
brew reinstall --cask ssh2fa \
&& xattr -dr com.apple.quarantine /Applications/SSH2FA.app \
&& open /Applications/SSH2FA.appNew install: brew install --cask gasvn/tap/ssh2fa, then run the same xattr and open commands above.
Universal Apple Silicon + Intel build. Requires macOS 26.
This release uses SSH2FA's stable local signing certificate but is not Apple-notarized. On a first manual install, macOS may require the xattr command above or System Settings → Privacy & Security → Open Anyway.
DMG SHA-256: f5925638d68915dc1d078812bd747027750f50c170092cbf0ea180e949d77d21
SSH2FA v1.5.11
简体中文
“已连接”现在真的表示 SSH 可用
- SSH2FA 会验证 ControlMaster 能否真正承载会话,不再把“进程还活着但连接不可用”显示为 Connected。
- 自动识别用户已有的
ControlMaster/ControlPath配置,并默认复用温连接;不再要求用户完成一组复杂设置。 - App 与后台服务短暂重连时使用中性的恢复状态;只有真正失败才显示错误和 Troubleshoot。
彻底处理反复密码提示
- daemon 使用跨版本稳定的证书身份;发布脚本会拒绝会导致每次升级重新授权 Keychain 的 ad-hoc 签名。
- 凭据读取和旧数据迁移合并,避免一个主机、一个字段地反复弹出 macOS 密码确认。
升级和排障不再断开 SSH
- 后台服务升级后会接管现有 ControlMaster,而不是关闭它们并强迫用户重新 SSH/2FA。
- 仅 UI 变化或普通重开 App 不再无故重启后台服务。
- Troubleshoot 的服务重启也会保留现有 SSH 连接。
UI 与默认行为
- 修复失败状态下 Retry 按钮被底层悬浮按钮遮挡、实际无法点击的问题。
- 不再向用户暴露 “helper”、daemon、IPC、ControlMaster 等内部术语,也不再把后台恢复误报为 “Connection lost”。
- 删除误导性的后台服务开关;SSH2FA 默认自动管理需要的服务和复用配置。
- 错误信息统一转换为用户可以直接处理的说明。
可靠性
- 包含
v1.5.1之后的 7 轮 daemon、状态持久化、输入校验和 SSH 恢复修复。 - 261 个 macOS 测试和 615 个 Rust 测试通过,0 失败。
English
- Connected now requires a ControlMaster that can actually carry an SSH session.
- Existing
ControlMaster/ControlPathsettings are detected and warm reuse is the default. - Stable daemon signing identity prevents every release from re-requesting access to every Keychain item.
- App updates, service recovery, and Troubleshoot preserve live SSH masters instead of forcing another SSH/2FA login.
- UI-only updates and ordinary app relaunches no longer restart a healthy background service.
- Retry remains clickable after failures, and internal helper/daemon/IPC terminology no longer leaks into the UI.
- Includes seven additional rounds of daemon, persistence, input-validation, and recovery hardening since v1.5.1.
Install / Update
brew install --cask gasvn/tap/ssh2fa \
&& xattr -dr com.apple.quarantine /Applications/SSH2FA.app \
&& open /Applications/SSH2FA.appAlready installed: brew reinstall --cask ssh2fa, or download SSH2FA.dmg below.
Universal Apple Silicon + Intel build. Requires macOS 26.
This release uses SSH2FA's stable local signing certificate but is not Apple-notarized yet. macOS may require the xattr command above or System Settings → Privacy & Security → Open Anyway.
DMG SHA-256: ccbd8358e13304870082acc65a67386210203107a65a602ec3020d9a8d8551da
SSH2FA v1.5.1
简体中文 / Simplified Chinese
The interface is now fully localized. 380 strings, including the runtime status
and error text (connection state, ssh failure explanations, Keychain guidance) —
that text is built as plain String, so it was invisible to string extraction
and would have stayed English no matter how complete the catalog looked.
Set it per-app in System Settings → General → Language & Region → Applications.
macOS stops asking for every saved password
Credentials lived in two Keychain items per host. macOS asks permission per
item whenever the reading binary's identity changes — which it does on every
release, because the background helper is separately signed. Six hosts meant
twelve "Always Allow" prompts after each update.
They now live in a single item, folded in automatically the first time the old
ones are read (so the migration costs no extra prompt). After this update you
authorize once, not once per host.
Mounts are usable now
- Pin the folders you work in. Mounting takes you straight there instead of
to the filesystem root.⋯ → Mountlists them; also in the menu bar and⌘K. - Browse to a folder instead of typing an absolute remote path — over the
connection you already have, so no extra login. - Auto-mount a pinned folder the moment its host connects.
- Several folders at once per host, each at
~/Mounts/<host>/<folder>. - Opens in Finder after mounting.
- Force-unmount for when the network dropped and Finder is beachballing on a
wedged mount.
See and change what's stored
Every host has a Password & setup view: where the alias points, whether a
password and 2FA secret are saved, which account the secret belongs to. Reveal
them behind Touch ID, replace either after a server password change or a 2FA
re-enrolment, and verify with Test login. Hosts can also be removed now —
previously they could only be added.
Copied secrets stay on this Mac (no Universal Clipboard hand-off to your other
devices) and clear themselves after 45 seconds.
Fixes
- Updating no longer drops your connections. The app restarted the helper
withlaunchctl kickstart -k, whose SIGTERM triggers a graceful shutdown that
closes every ControlMaster — so every update forced a fresh 2FA on every host. - The app could stop reconnecting for good. After roughly four minutes of an
unreachable helper it gave up permanently, sitting on "Reconnecting…" with a
healthy helper on the other end until relaunched. - Tunnel ports are editable, keeping the tunnel's tags, post-connect hook and
URL suffix (changing them used to mean delete-and-recreate). - Keychain errors say what to do about them instead of showing raw internal text.
- VoiceOver labels on icon-only buttons.
Install
brew install --cask gasvn/tap/ssh2fa \
&& xattr -dr com.apple.quarantine /Applications/SSH2FA.app \
&& open /Applications/SSH2FA.appAlready installed? brew reinstall --cask ssh2fa, or download the DMG below.
Signed but not notarized, so macOS quarantines it on download — the xattr line
clears that, or use System Settings → Privacy & Security → "Open Anyway".
Universal (Apple Silicon + Intel). Requires macOS 26.
SSH2FA v1.1.0
See and change what SSH2FA has stored
Until now there was no way to look at — let alone change — the credentials SSH2FA
keeps for a host. A server-side password change or a 2FA re-enrolment meant
deleting the host and adding it again.
Every host now has a Password & setup view. Open it by double-clicking a host
row, from its ⋯ menu, or with ⌘K.
- Connection — see where the alias actually points (server address, username,
port). Editable for hosts SSH2FA set up; read-only for aliases you defined in
your own~/.ssh/config, which it still never rewrites. - SSH password — see whether one is stored and reveal it behind Touch ID (or
your Mac password), or replace it after changing it on the server. - Two-factor secret — which account it belongs to, its live code, the setup
URL on request, and a way to replace it by paste or QR. - Test login — verifies the stored credentials with a one-shot login that
can't reuse an existing connection. The secrets stay in the background helper;
nothing is pulled into the app to check them.
Revealing a secret always asks for Touch ID / your Mac password, even when the
app lock is off, and every reveal is written to the helper's log.
Fixed: the app could stop reconnecting for good
If the background helper was unreachable for more than about four minutes — a
helper update, a long sleep — the app gave up and never tried again. It sat on
"Reconnecting to the background helper…" with a healthy helper on the other end,
until you quit and reopened it. Recovery is now unbounded: it keeps retrying, and
reconnects on its own within seconds of the helper coming back.
Also: Keychain errors now say what to do about them ("choose Always Allow")
instead of showing raw internal text.
Install
brew install --cask gasvn/tap/ssh2fa \
&& xattr -dr com.apple.quarantine /Applications/SSH2FA.app \
&& open /Applications/SSH2FA.appAlready installed? brew reinstall --cask ssh2fa, or download the DMG below.
The app is signed but not notarized, so macOS quarantines it on first download —
the xattr line above clears that, or use System Settings → Privacy & Security →
"Open Anyway".
Note on updating: the background helper is a new binary, so macOS asks
permission the first time it reads each saved credential. Choose Always Allow
and it won't ask again.
Universal (Apple Silicon + Intel). Requires macOS 26.
SSH2FA v1.0.3
Highlights
Self-healing daemon connection
Fixes the case where the app showed "Daemon is slow to respond" and stayed
stuck until you manually quit and reopened it — even though the background
helper was perfectly healthy the whole time.
- The real cause was a dropped connection the app never noticed: after sleep
a unix socket can go silently half-open (the OS never reports it), and if the
app happened to start while the helper was restarting, it never established a
connection at all. Either way the app sat there timing out forever. - Now the poll is treated as a heartbeat: after a few consecutive misses the app
tears down the dead connection and reconnects on its own, in both cases.
Clearer status messages
The old connection banners were confusing and often blamed the wrong thing
("daemon" is jargon, and the daemon was usually fine). Reworded the whole family
in plain language and to say what's actually happening:
- "Daemon is slow to respond / disconnected — retrying…" → "Reconnecting to
the background helper…" - "Daemon unreachable — is ssh2fa-daemon running?" → "Connecting to the
background helper…" - "Daemon lost" / "Daemon reconnected" → "Connection lost" / "Reconnected"
- Settings "Restart daemon" → "Restart background helper"
Install
Un-notarized build, so macOS Gatekeeper needs one extra step:
Homebrew (recommended):
brew install --cask gasvn/tap/ssh2fa && xattr -dr com.apple.quarantine /Applications/SSH2FA.app && open /Applications/SSH2FA.app
(already installed? brew reinstall --cask gasvn/tap/ssh2fa)
Manual: download the DMG → drag to Applications → System Settings → Privacy & Security → "Open Anyway".
Requires macOS 26. You'll need your 2FA secret key (TOTP setup key) — SSH2FA types the codes; it does not work with Duo Push.
SSH2FA v1.0.2
Highlights
More stable SSH connections
Fixes the periodic "shows connected but stuck for ~a minute" behavior after sleep/wake or switching networks (Wi-Fi roam, VPN up/down, Ethernet):
- No more needless re-logins of live connections. The liveness check now uses an authoritative
ssh -O checktiebreaker, so a momentarily-busy master is kept (adopted) instead of being torn down and forced through a fresh 2FA. - Staggered reconnects + network-settle delay. After a wake/network change the whole fleet no longer re-authenticates in one thundering instant — connections are spread out and given a moment for the new network to come up, which removes the self-inflicted load that was causing false "dead" reads and 60-second login timeouts.
- Smarter wake debouncing so a settling network's IP flaps don't restart the reconnect storm.
- Boot cleanup of stale control-socket symlinks left by older versions.
UX polish (35 improvements)
- Failures are now visible and actionable: dashboard error banners with a Troubleshoot shortcut, inline failure reasons on host/tunnel rows, and clearer batch-operation reporting.
- Menu-bar icon turns into a warning triangle (a shape, not just a color) when something needs attention.
- Add Host: shows the resulting SSH alias, a Return-key "Test login → Add" flow, success confirmation, and no more orphaned host if you cancel after testing.
- Tunnels: one-click Copy URL, collapsed
:portdisplay, clearer "Pick node & start". - Settings reorganized everyday → advanced, with accurate captions; Touch ID lock now re-locks correctly.
- Command Palette is now reachable from the menu bar.
Install
Un-notarized build (no $99 Apple Developer ID), so macOS Gatekeeper needs one extra step:
Homebrew (recommended):
brew install --cask gasvn/tap/ssh2fa && xattr -dr com.apple.quarantine /Applications/SSH2FA.app && open /Applications/SSH2FA.app
(already installed? brew reinstall --cask gasvn/tap/ssh2fa)
Manual: download the DMG → drag to Applications → System Settings → Privacy & Security → "Open Anyway".
Requires macOS 26. You'll need your 2FA secret key (TOTP setup key) — SSH2FA types the codes; it does not work with Duo Push.
SSH2FA v1.0.1
SSH2FA v1.0.1
Fixes
- Network switches reconnect fast. After switching Wi-Fi / VPN / Ethernet, SSH2FA no longer shows "connected" while nothing actually works. A network-dead connection is now detected in ~45s (was ~3 min), and a network change proactively rebuilds the affected connections — including Wi-Fi→Wi-Fi switches.
- "Require Touch ID to open the dashboard" now works the moment you toggle it, and re-locks when the window leaves the foreground.
New
- Update reminder. SSH2FA checks once a day for a newer release and flags it in the menu bar. The About tab shows one-step update instructions (Homebrew + manual) and lets you skip a version. Opt out in Settings → General. It never downloads or installs anything on its own.
Menu bar
- Clicking the menu-bar icon now opens the menu (left- or right-click); Open SSH2FA is the first item.
- Removed the alive/total number (the icon color already shows connection health).
- Moved Uninstall into Settings → Troubleshoot.
Updating
- Homebrew:
brew reinstall --cask ssh2fa && xattr -dr com.apple.quarantine /Applications/SSH2FA.app && open /Applications/SSH2FA.app - Manual: download
SSH2FA.dmgbelow, drag to /Applications. The build isn't notarized, so on first launch: right-click → Open (orxattr -dr com.apple.quarantine /Applications/SSH2FA.app).
Requires macOS 26 (Tahoe) or later.