Skip to content

feat(ssh): add None auth method for passwordless tunnels#1909

Merged
datlechin merged 1 commit into
mainfrom
feat/ssh-none-auth
Jul 18, 2026
Merged

feat(ssh): add None auth method for passwordless tunnels#1909
datlechin merged 1 commit into
mainfrom
feat/ssh-none-auth

Conversation

@datlechin

Copy link
Copy Markdown
Member

Closes discussion #1907.

Problem

You can't reach a database over an SSH tunnel when the SSH host uses Tailscale SSH. Tailscale authenticates the peer at the tailnet layer, so its SSH server accepts the connection with the SSH none method and expects no password or key. Leaving TablePro's SSH password blank fails with "SSH password rejected", because the .password arm of buildAuthenticator throws when credentials.sshPassword == nil, before any libssh2 call. TablePro's auth model had no way to authenticate with no credentials.

Fix

Add an explicit None SSH auth method backed by a NoneAuthenticator.

NoneAuthenticator uses the documented libssh2 idiom: libssh2_userauth_list() sends a none userauth request on the wire; if the server accepts (Tailscale accept mode), it returns NULL and libssh2_userauth_authenticated() returns 1, so the tunnel is up. If the server offers methods instead, it throws a new .passwordlessRejected reason that tells you to pick a method that sends credentials. Auth runs in blocking mode, so there is no EAGAIN case to handle.

Both SSH forms (inline and profile editor) get an explicit None branch that hides the credential fields and shows a short hint. An explicit method beats a silently-blank password: it is discoverable, it removes the ambiguity between "I forgot my password" and "this host needs none", and it is a deliberate contract rather than an accident of libssh2 behavior (which broke for blank-password clients on Tailscale 1.80.0).

Hardening in the same area

  • Tolerant authMethod decode. SSHConfiguration.init(from:) decoded authMethod with a throwing decode, and because the tunnel JSON is read under try?, an older app reading a None connection synced from a newer Mac silently decoded the whole SSH config as disabled, dropping host, port, and jump hosts. It now falls back to Password and keeps the tunnel. This caps the blast radius of every future auth-method addition.
  • URL round-trip. A sshNoAuth=true flag so a None config exported to a scheme+ssh:// URL or deeplink round-trips instead of reverting to Password.

Tests

  • None builds a NoneAuthenticator; the nil-password guard throws (regression for the reported bug).
  • .passwordlessRejected produces a distinct, non-empty message.
  • Unknown authMethod decodes to Password and preserves host/port; None round-trips through Codable.
  • URL formatter and parser round-trip sshNoAuth.

Docs / CHANGELOG

  • docs/databases/ssh-tunneling.mdx documents the None method and its Tailscale use.
  • CHANGELOG Added entry under [Unreleased].

Notes

  • The live none handshake needs a real Tailscale SSH host, so it is covered here by the build and unit tests; worth a manual check on a tailnet.
  • Jump/bastion hosts are unchanged: SSHJumpHost uses a separate SSHJumpAuthMethod (private key / agent only), and Tailscale SSH is the tunnel target, not the bastion.
  • No PluginKit ABI impact; SSHAuthMethod is app-level.

@mintlify

mintlify Bot commented Jul 18, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jul 18, 2026, 1:35 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@datlechin
datlechin merged commit cffb1c8 into main Jul 18, 2026
3 checks passed
@datlechin
datlechin deleted the feat/ssh-none-auth branch July 18, 2026 13:37

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3620acfb7c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

case privateKey = "Private Key"
case sshAgent = "SSH Agent"
case keyboardInteractive = "Keyboard Interactive"
case none = "None"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add None to the shared SSH auth model

This introduces a new persisted raw value ("None") for Mac connections, but the shared model used by TableProMobile/TableProSync (Packages/TableProCore/Sources/TableProModels/SSHConfiguration.swift) still only recognizes password/privateKey/sshAgent/keyboardInteractive and falls back to password for anything else. A no-auth SSH connection synced or imported into the mobile/shared path will therefore be decoded as password auth, and a later mobile sync/export can write it back that way, silently downgrading a Tailscale/no-auth tunnel so it fails on Mac. Please preserve/add the None case in the shared model and mobile handling, even if mobile only reports it as unsupported.

Useful? React with 👍 / 👎.

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.

1 participant