Skip to content

fix(security): close credential exfiltration, host key, plugin load and supply chain gaps - #2087

Merged
datlechin merged 23 commits into
mainfrom
fix/security-hardening
Aug 11, 2026
Merged

fix(security): close credential exfiltration, host key, plugin load and supply chain gaps#2087
datlechin merged 23 commits into
mainfrom
fix/security-hardening

Conversation

@datlechin

Copy link
Copy Markdown
Member

Security review of the app and its build pipeline, with the fixes. Every finding below was traced to a concrete code path before being fixed, and three hypotheses were refuted with evidence rather than patched.

Highest impact

A shared link could steal an AWS credential. tablepro://import?...&af_awsAuth=profile&af_awsRDSEndpoint=prod-db... made TablePro read ~/.aws/credentials, mint a SigV4 RDS token signed for the real production endpoint, and send it as the password to the link author's host, with sslmode=require which does not verify certificates. The import filter was a denylist of exactly one key. It is now a prefix and key block on every credential-resolution field, and RDSSigningEndpointResolver only honours an endpoint override when the socket is a local forward or the override restates the host being dialled.

Release workflows ran third-party actions from mutable tags. setup-xcode@v1 and action-gh-release@v1 sat in jobs holding the Developer ID certificate, the notarization password, and the Sparkle private key. A moved tag meant a signed, notarized, Sparkle-valid update to every user. All nine uses are pinned to a commit.

Mobile never checked SSH host keys. SSHTunnel.handshake() handshook and authenticated without calling libssh2_session_hostkey. There was no known-hosts store and no prompt anywhere in the mobile target, so anyone on the path received the SSH password in cleartext. Mobile now has the same store and verification as the Mac, with a prompt whose default button is Cancel.

libssh2 CVE-2026-55199. The SSH_MSG_EXT_INFO handler read nr-extensions as an untrusted uint32 and looped that many times while ignoring every _libssh2_get_string() failure. A server declaring 0xFFFFFFFF pinned a CPU core before authentication. Patched, rebuilt for macOS and iOS, published, checksums updated.

The rest

  • A changed SSH host key was reported as a first-use prompt when the server offered a different key algorithm, because the store matched on host and key type together. It now decides on the host.
  • Trusting an unknown SSH host key was the default button, so a stray Return pinned an attacker's key.
  • Plugin signatures were verified by path and never rechecked before dlopen. Verification now runs immediately before the load, and again before a staged update replaces the installed copy.
  • MySQL and MariaDB left LOAD DATA LOCAL INFILE at the connector default. Disassembly of the shipped libmariadb showed a server cannot initiate the request, but the armed-query path could still substitute a file. Now disabled outright.
  • connections.json was unauthenticated, so anything that could write to the home folder could add a passwordSource of kind command and have it executed on the next session restore, which is the default startup behaviour. The file is now bound to a keychain-held HMAC key. Password sources do not run when it changes outside the app, and loading connections is never blocked.
  • SQL Server Verify CA and Verify Identity encrypted without checking anything while the picker said otherwise. FreeTDS takes certificate settings from a config file rather than the connection, so a verifying mode now writes a one-entry config and scopes FREETDSCONF to that dbopen call, using the macOS system roots unless a CA is supplied.
  • ClickHouse Verify CA returned nil for a PEM authority and fell back to the public root store in silence. It reads PEM now and fails closed.
  • Mobile wrote database passwords to iCloud Keychain unconditionally while the settings copy promised they stayed on device, and the Mac had made it a preference for years. There is a Sync Passwords toggle now, off by default.
  • Mobile dropped a connection's stored TLS configuration on save for every type except SQL Server and Oracle.
  • The MCP server had no DNS-rebinding defence and dispatched /v1/integrations/exchange before authentication. A request carrying a disallowed browser Origin is now refused before routing.
  • list_connections named connections the user had marked AI Never.
  • Copy Connection String put plaintext passwords on the pasteboard with no concealed-type marker, so clipboard-history apps kept them.
  • pg_dump stderr was logged at privacy: .public, publishing internal hostnames and role names.
  • Plugin downloads now require https, and the hosts TablePro itself calls are pinned to TLS 1.2 with forward secrecy.

Refuted, not fixed

Three findings did not survive investigation and are recorded so nobody re-opens them:

  • Team Library could not run a teammate's startup SQL. connectToLinkedConnection rebuilds seven fields and routing resolves by UUID against local storage, so nothing reached a driver. The field is stripped from the cached pull anyway, before the consuming feature exists.
  • There is no dual-OpenSSL conflict. A symbol scan across all fifteen archives shows only libcrypto defines OpenSSL symbols; libcassandra imports seventy and resolves them against the one 3.4.1 dylib pair. The "OpenSSL 3.6.1" string is DataStax telemetry metadata.
  • Query history and the audit database at mode 0644 are not readable by other local accounts. ~/Library and ~/Library/Application Support are both drwx------.

Testing

23 tests added. 80 pass across every suite this branch touches, swiftlint --strict is clean, and both the macOS app and the iOS app build.

Two things I could not verify here. SQL Server certificate rejection needs a live server: point a Verify Identity connection at a host whose name does not match its certificate and confirm the connect fails. And the libssh2 rebuild is verified by the patch applying cleanly to pristine 1.11.1 and by both CVE patches appearing in the build log, not by a hostile server.

Known gaps

Redis Verify Identity still cannot check the hostname, because the shipped hiredis never calls X509_check_host. Closing it needs a hiredis relink or a driver-side check. The iOS connection form still offers only an SSL on/off toggle for MySQL, PostgreSQL and Redis; those files are being edited on another branch, so they were left alone.

https://claude.ai/code/session_0116PJisD2G7jZuVtQkbU1dC

@mintlify

mintlify Bot commented Aug 11, 2026

Copy link
Copy Markdown

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

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Aug 11, 2026, 10:53 AM

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

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

# Conflicts:
#	CHANGELOG.md
#	TableProMobile/TableProMobile/ViewModels/ConnectionFormViewModel.swift
@datlechin
datlechin merged commit 0538952 into main Aug 11, 2026
5 checks passed
@datlechin
datlechin deleted the fix/security-hardening branch August 11, 2026 12:04
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