VortexLatest version v1.2.0
US$10

desktop ssh client · one process · ~9 MB

You always know
which machine
you're on.

A terminal, an SFTP browser and your port forwards, in one window, over a host list grouped by environment. Each group picks a colour. That colour then follows the session everywhere: sidebar, tab, the edge of the terminal, the status bar. Production gets a colour nothing else is allowed to have, plus a broken line, so it survives a cheap monitor and a screenshot pasted into Slack.

Ten dollars, once. The next version costs the same ten. Nothing recurs.

Break this one and you fix it before lunch.
Break this one and somebody's phone goes off.

the four surfaces

One connection, four surfaces.

A session is one SSH connection with a terminal, a file browser and your forwards layered over it. Moving between them doesn't reconnect. The inactive pane stays mounted and hidden, so you never end up with a second login you didn't ask for.

terminal

The terminal is allocated on the far side

SSH runs inside the app, through Go's own implementation. It does not drive the system ssh binary, and the pseudo-terminal is requested on the remote host over the SSH channel, so nothing on your machine ever allocates one.

The practical result is that Windows gets the same terminal as macOS, with ConPTY nowhere in the path. vim renders. htop renders. Drag the window and the remote terminal resizes with it.

  • Geometry in the status bar, so you can see what the far side thinks the size is
  • Jump hosts, with the host key checked at every hop
  • Output capped at 4 MB; the reader parks until the screen catches up
  • One session per tab, and the tab keeps its colour

files

SFTP over the connection you already opened

One channel over from the terminal, so the browser opens on a session you've already authenticated. Crumbs, mode bits, sizes and times, in columns that line up, because the whole interface is monospace and all of that is data that wants to align. Downloads land at 0600, and get chmod'd on the descriptor too, since the file you just pulled is usually a kubeconfig.

  • Upload and download with progress, and a cancel that actually cancels
  • A recursive delete unlinks a symlink; it does not empty what the link points at
  • The local path stays in Go, behind a one-shot token from the native picker

tunnels

Forwards you can see

Every forward is a row: the route, how many connections are open through it, what it's moving. They bind loopback unless you say otherwise, which is what OpenSSH means by GatewayPorts no, and the app tells you the address it bound instead of the one you typed.

  • -L and -R, opened and closed without dropping the session
  • A wildcard address asks first; loopback just works

identities

Keys made here, kept properly

Make an ed25519 or RSA key without leaving the window. Give it a passphrase and it's encrypted before it's written, so the plain key never touches the disk; the file lands O_EXCL 0600. Unlocking happens at connect time and the derived signer is what gets cached, so it asks once per key per launch and keeps the passphrase nowhere.

  • A recorded key path outside the key directory is refused, not followed
  • A one-shot ~/.ssh/config import to start from

security

The parts that have to be right.

A review pass went through the security-relevant surface reading the library source, not assumptions about it. Most of what got checked was already fine. What wasn’t is below, and every one of these got a test that fails without its fix first, which is the only reason to believe any of it.

Algorithms

The allowlist is x/crypto’s SupportedAlgorithms, not its wider defaults. No ssh-rsa over SHA-1, no ssh-dss, no hmac-sha1-96, no diffie-hellman-group14-sha1.

There is a deny-list test, so the day upstream widens its defaults again, the build fails instead of the client quietly getting looser.

Host keys

Checked on first contact and at every hop of a jump chain. A key that changed stops the connection. A key that is new asks once, and doing nothing counts as no.

A man in the middle picks from whatever the client offers, which is why the allowlist above is the thing stopping a weak host key from getting pinned into your known_hosts through this app. OpenSSH would then honour it too.

The handshake

A watchdog closes the connection if it stalls. Not a read deadline: a jumped hop’s connection doesn’t support one, so a deadline would have worked for direct dials and silently done nothing for the jumped ones.

Before it, a peer that finished TCP and went quiet held a goroutine open with no way to reclaim it. The test hangs until the binary’s panic timeout without the fix.

Port forwards

Loopback, unless you say otherwise. It’s what OpenSSH means by GatewayPorts no. You get told the address that was actually bound, not the one you typed.

The prompt only appears when the address really is a wildcard, so the safe answer is the default and saying no still leaves you a working tunnel.

Files written

Keys created O_EXCL 0600. Host and identity stores rewritten atomically at 0600. Downloads created 0600, then chmod’d on the descriptor, because the mode argument only applies to a file that didn’t already exist.

os.Create opens 0666. Everything this app writes for you is something you would not leave at 0644.

Remote output

Capped at 4 MB, and the reader stops reading until the screen drains. Not reading is what applies backpressure across the SSH window.

Measured at 205 MB buffered in half a second before the cap. A hostile host could exhaust the client from a single cat.

Two-factor prompts

One prompt per keyboard-interactive round. An echo-off prompt is not proof that something wants your password; PAM asks for a TOTP code exactly the same way.

The test asserts on what the server saw, and before the fix it saw the password twice: once in the code field.

Deletes

Lstat first, so a recursive remove unlinks a symlink instead of walking through it. The library’s own RemoveAll resolves the path first, and a link to /etc emptied /etc.

Links in output

A hyperlink from a remote host gets parsed with URL, allowed only if it’s http or https, and handed to your browser. Nothing from the far side can navigate the webview holding the bridge.

Passphrases

Used once, to derive a signer. The signer is cached. The passphrase is kept nowhere and written nowhere.

And one that is still open

On macOS a stored password goes through /usr/bin/security, so the keychain item’s access control binds to that tool, not to Vortex. Anything else running as you can read it, with no prompt. That is closer to a 0600 file than to a per-app credential. Fixing it needs a stable signing identity, so it’s a piece of scheduled work and not a footnote, and the About tab in the app says the same thing. Whether a production password belongs in there is your call, and you should get to make it knowing that.

Nothing in the negotiation, bind or write path is weaker than OpenSSH’s default, and every security claim in the documentation is one the code actually makes. Three of the findings above were the documentation being wrong, which is its own kind of bug.

reliability

Boring, on purpose.

An SSH client earns its keep by being unremarkable for months at a time. This is what runs before a build turns into one you would be handed.

$ go test ./internal/... -raceok vortex/internal/sshxok vortex/internal/hoststoreok vortex/internal/identityok vortex/internal/secretok vortex/internal/settingsok vortex/internal/services192 tests · race detector on · goleak active · no skips$ npm test120 tests · framing, session reducer, paths, jump chains, retry policy

Real servers, not mocks

The SSH, SFTP and forwarding tests run against servers started inside the test binary, so what gets exercised is an actual handshake on an actual channel. Two things that setup can’t prove, that vim renders and that a window resize reaches the far side, run against a real sshd instead.

It reconnects a drop, not an exit

A transport error or a signal reconnects, with backoff. A clean shell exit never does, and no exit status reopens a tab you closed. A host-key failure isn’t retried at all: the wording of that error is pinned by a test, precisely so nobody can reword it and quietly put it back on the retry path.

A leaked goroutine fails the test

goleak runs across the packages. A session that leaks its reader, a forward that leaves an accept loop behind, a dial nobody reclaimed: caught in the run, instead of three hours into your Tuesday.

One process, and the webview you already have

The UI renders in the webview the OS ships. No Chromium in the download, and nothing serving the frontend over a port in the build you run. Around 9 MB, and one process to trust.

Switching panes doesn’t open a session

Going from the terminal to Files and back unmounts nothing. That’s a deliberate constraint in the shell, because a remount would mint a new session id and open a second login on a host that is very likely logging them.

I built this because every SSH client I tried was either a terminal with a host list bolted onto the side, or a subscription.

Vortex is one binary. It costs ten dollars, it runs on your machines, and I am not going to email you about it. The colour rule at the top of this page exists because I once restarted the wrong Postgres, and I have never fully recovered from finding out how ordinary that afternoon looked while I was doing it.

Two things I would rather you heard here than found out later. The Files and Tunnels panes are tested on the Go side and have had far less time in front of my eyes than the terminal has. And the stored-password limit in the section above is real, it is written down in the app, and it is on the list.

If that is the wrong trade for you, don’t buy it. If it’s the right one, it’s ten dollars, and the next version will also be ten dollars.

BlazeOps Enterprise · Malaysia

price

US$10. Once.

Then it’s yours, and it stays yours. When a new version lands it’s ten dollars again. That is the entire pricing model, and there is no second page explaining it.

VORTEXdesktop ssh client
1 × download10.00
licencepersonal or team
seatsnot counted
termforever
platformsmacos · windows · linux
totalUS$10.00
next versionUS$10.00

Whenever there is one, at the same price. Nothing recurring, nothing that switches off.

Download for US$10

Final sale, no refunds. Terms.

What ten dollars buys

The build you download, for macOS, Windows or Linux, and the right to run it on your machines or your team’s. No seat count, no expiry, no activation, no account. It keeps working whether or not you ever pay us again, which is the part most of this page is really about.

Updates

Ten dollars again. A new version is a new download at the same price, so there is no renewal date to diary and no upgrade tier that depends on when you first bought.

If you skip one

Nothing happens. The copy on your disk doesn’t phone home, expire, nag, or lose a feature because a newer build exists somewhere.

Vortex

macOS, Windows and Linux. Around 9 MB, and no runtime to install.

Made in Malaysia.